13

レクサーを試していますが、プログラムの一部でwhileループからifステートメントとdo-whileループに切り替えると、コードが最大20%高速になり、クレイジーに見えました。コンパイラで生成されたコードの違いをこれらのアセンブリスニペットに分離しました。なぜ速いコードが速いのか誰かが知っていますか?

アセンブリでは、「edi」は現在のテキスト位置、「ebx」はテキストの終わり、「isAlpha」は文字がアルファベットの場合は1、それ以外の場合は0のルックアップテーブルです。

スローコード:

slow_loop:
00401897  cmp   edi,ebx 
00401899  je    slow_done (4018AAh) 
0040189B  movzx eax,byte ptr [edi] 
0040189E  cmp   byte ptr isAlpha (4533E0h)[eax],0 
004018A5  je    slow_done (4018AAh) 
004018A7  inc   edi  
004018A8  jmp   slow_loop (401897h) 
slow_done:

高速コード:

fast_loop:
0040193D  inc   edi  
0040193E  cmp   edi,ebx 
00401940  je    fast_done (40194Eh) 
00401942  movzx eax,byte ptr [edi] 
00401945  cmp   byte ptr isAlpha (4533E0h)[eax],0 
0040194C  jne   fast_loop (40193Dh) 
fast_done:

文字「a」のみで構成されるメガバイトのテキストに対してこれらのアセンブリスニペットだけを実行すると、高速コードは30%高速になります。私の推測では、ブランチの予測ミスのためにスローコードは遅いと思いますが、ループでは1回限りのコストになると思いました。

両方のスニペットをテストするために使用したプログラムは次のとおりです。

#include <Windows.h>
#include <string>
#include <iostream>

int main( int argc, char* argv[] )
{
    static char isAlpha[256];
    for ( int i = 0; i < sizeof( isAlpha ); ++i )
        isAlpha[i] = isalpha( i ) ? 1 : 0;

    std::string test( 1024*1024, 'a' );

    const char* start = test.c_str();
    const char* limit = test.c_str() + test.size();

    DWORD slowStart = GetTickCount();
    for ( int i = 0; i < 10000; ++i )
    {
        __asm
        {
            mov edi, start
            mov ebx, limit

            inc edi

        slow_loop:
            cmp   edi,ebx
            je    slow_done
            movzx eax,byte ptr [edi]
            cmp   byte ptr isAlpha [eax],0
            je    slow_done
            inc   edi
            jmp   slow_loop

        slow_done:
        }
    }
    DWORD slowEnd = GetTickCount();
    std::cout << "slow in " << ( slowEnd - slowStart ) << " ticks" << std::endl;

    DWORD fastStart = GetTickCount();
    for ( int i = 0; i < 10000; ++i )
    {
        __asm
        {
            mov edi, start
            mov ebx, limit

        fast_loop:
            inc   edi
            cmp   edi,ebx
            je    fast_done
            movzx eax,byte ptr [edi]
            cmp   byte ptr isAlpha [eax],0
            jne   fast_loop

        fast_done:
        }
    }
    DWORD fastEnd = GetTickCount();
    std::cout << "fast in " << ( fastEnd - fastStart ) << " ticks" << std::endl;

    return 0;
}

テストプログラムの出力は次のとおりです。

slow in 8455 ticks
fast in 5694 ticks
4

2 に答える 2

12

申し訳ありませんが、GCC(linux)でコードを正確に再現することはできませんでしたが、いくつかの結果が得られ、主要なアイデアがコードに保存されたと思います。

コードフラグメントのパフォーマンスを分析するためのIntelのツールがあります:http : //software.intel.com/en-us/articles/intel-architecture-code-analyzer/(Intel IACA)。ダウンロードしてテストするのは無料です。

私の実験では、スローループについて報告します。

Intel(R) Architecture Code Analyzer Version - 2.0.1
Analyzed File - ./l2_i
Binary Format - 32Bit
Architecture  - SNB
Analysis Type - Throughput

Throughput Analysis Report
--------------------------
Block Throughput: 3.05 Cycles       Throughput Bottleneck: Port5

Port Binding In Cycles Per Iteration:
-------------------------------------------------------------------------
|  Port  |  0   -  DV  |  1   |  2   -  D   |  3   -  D   |  4   |  5   |
-------------------------------------------------------------------------
| Cycles | 0.5    0.0  | 0.5  | 1.0    1.0  | 1.0    1.0  | 0.0  | 3.0  |
-------------------------------------------------------------------------

N - port number or number of cycles resource conflict caused delay, DV - Divide
D - Data fetch pipe (on ports 2 and 3), CP - on a critical path
F - Macro Fusion with the previous instruction occurred

| Num Of |              Ports pressure in cycles               |    |
|  Uops  |  0  - DV  |  1  |  2  -  D  |  3  -  D  |  4  |  5  |    |
---------------------------------------------------------------------
|   1    |           |     |           |           |     | 1.0 | CP | cmp edi,
|   0F   |           |     |           |           |     |     |    | jz 0xb
|   1    |           |     | 1.0   1.0 |           |     |     |    | movzx ebx
|   2    |           |     |           | 1.0   1.0 |     | 1.0 | CP | cmp cl, b
|   0F   |           |     |           |           |     |     |    | jz 0x3
|   1    | 0.5       | 0.5 |           |           |     |     |    | inc edi
|   1    |           |     |           |           |     | 1.0 | CP | jmp 0xfff

高速ループの場合:

Throughput Analysis Report
--------------------------
Block Throughput: 2.00 Cycles       Throughput Bottleneck: Port5

Port Binding In Cycles Per Iteration:
-------------------------------------------------------------------------
|  Port  |  0   -  DV  |  1   |  2   -  D   |  3   -  D   |  4   |  5   |
-------------------------------------------------------------------------
| Cycles | 0.5    0.0  | 0.5  | 1.0    1.0  | 1.0    1.0  | 0.0  | 2.0  |
-------------------------------------------------------------------------

N - port number or number of cycles resource conflict caused delay, DV - Divide
D - Data fetch pipe (on ports 2 and 3), CP - on a critical path
F - Macro Fusion with the previous instruction occurred

| Num Of |              Ports pressure in cycles               |    |
|  Uops  |  0  - DV  |  1  |  2  -  D  |  3  -  D  |  4  |  5  |    |
---------------------------------------------------------------------
|   1    | 0.5       | 0.5 |           |           |     |     |    | inc edi
|   1    |           |     |           |           |     | 1.0 | CP | cmp edi,
|   0F   |           |     |           |           |     |     |    | jz 0x8
|   1    |           |     | 1.0   1.0 |           |     |     |    | movzx ebx
|   2    |           |     |           | 1.0   1.0 |     | 1.0 | CP | cmp cl, b
|   0F   |           |     |           |           |     |     |    | jnz 0xfff

したがって、スローループでは、JMPはクリティカルパスの追加命令です。cmp + jz / jnzのすべてのペアは、単一のu-opにマージされます(マクロ融合)。そして、私のコードの実装では、重要なリソースはPort5であり、ALU + JMPを実行できます(JMP機能を備えた唯一のポートです)。

PS:誰かがポートがどこにあるかわからない場合は、最初の 2番目に写真があります。および記事:rwt

PPS:IACAにはいくつかの制限があります。CPU(実行ユニット)の一部のみをモデル化し、キャッシュミス、ブランチの予測ミス、さまざまなペナルティ、周波数/電力の変更、OS割り込み、実行ユニットのハイパースレッディング競合などの多くの影響を考慮していません。ただし、最新のIntel CPUの最も内部的なコアの内部を簡単に確認できるため、便利なツールです。そして、それは内部ループに対してのみ機能します(この質問のループと同じように)。

于 2012-06-29T09:47:08.927 に答える
2

テストテキストを使用すると、反復ごとにループが完了し、高速ループの命令が1つ少なくなります。

于 2012-06-29T00:42:30.350 に答える