2017-09-26 23 views
2

約100億回のテストの後、imm64がAMD64の場合はm64よりも0.1ナノ秒早い場合、m64は高速ですが、わかりません。次のコードのval_ptrのアドレスは即値ですか?x86-64の方が速い、imm64またはm64のどちらですか?

# Text section 
.section __TEXT,__text,regular,pure_instructions 
# 64-bit code 
.code64 
# Intel syntax 
.intel_syntax noprefix 
# Target macOS High Sierra 
.macosx_version_min 10,13,0 

# Make those two test functions global for the C measurer 
.globl _test1 
.globl _test2 

# Test 1, imm64 
_test1: 
    # Move the immediate value 0xDEADBEEFFEEDFACE to RAX (return value) 
    movabs rax, 0xDEADBEEFFEEDFACE 
    ret 
# Test 2, m64 
_test2: 
    # Move from the RAM (val_ptr) to RAX (return value) 
    mov rax, qword ptr [rip + val_ptr] 
    ret 
# Data section 
.section __DATA,__data 
val_ptr: 
    .quad 0xDEADBEEFFEEDFACE 

測定コードは次のとおりです。だから、

#include <stdio.h>   // For printf 
#include <stdlib.h>   // For EXIT_SUCCESS 
#include <math.h>    // For fabs 
#include <stdint.h>   // For uint64_t 
#include <stddef.h>   // For size_t 
#include <string.h>   // For memset 
#include <mach/mach_time.h> // For time stuff 

#define FUNCTION_COUNT 2  // Number of functions to test 
#define TEST_COUNT  0x10000000 // Number of times to test each function 

// Type aliases 
typedef uint64_t rettype_t; 
typedef rettype_t(*function_t)(); 

// External test functions (defined in Assembly) 
rettype_t test1(); 
rettype_t test2(); 

// Program entry point 
int main() { 

    // Time measurement stuff 
    mach_timebase_info_data_t info; 
    mach_timebase_info(&info); 

    // Sums to divide by the test count to get average 
    double sums[FUNCTION_COUNT]; 

    // Initialize sums to 0 
    memset(&sums, 0, FUNCTION_COUNT * sizeof (double)); 

    // Functions to test 
    function_t functions[FUNCTION_COUNT] = {test1, test2}; 

    // Useless results (should be 0xDEADBEEFFEEDFACE), but good to have 
    rettype_t results[FUNCTION_COUNT]; 

    // Function loop, may get unrolled based on optimization level 
    for (size_t test_fn = 0; test_fn < FUNCTION_COUNT; test_fn++) { 
    // Test this MANY times 
    for (size_t test_num = 0; test_num < TEST_COUNT; test_num++) { 
     // Get the nanoseconds before the action 
     double nanoseconds = mach_absolute_time(); 
     // Do the action 
     results[test_fn] = functions[test_fn](); 
     // Measure the time it took 
     nanoseconds = mach_absolute_time() - nanoseconds; 

     // Convert it to nanoseconds 
     nanoseconds *= info.numer; 
     nanoseconds /= info.denom; 

     // Add the nanosecond count to the sum 
     sums[test_fn] += nanoseconds; 
    } 
    } 
    // Compute the average 
    for (size_t i = 0; i < FUNCTION_COUNT; i++) { 
    sums[i] /= TEST_COUNT; 
    } 

    if (FUNCTION_COUNT == 2) { 
    // Print some fancy information 
    printf("Test 1 took %f nanoseconds average.\n", sums[0]); 
    printf("Test 2 took %f nanoseconds average.\n", sums[1]); 
    printf("Test %d was faster, with %f nanoseconds difference\n", sums[0] < sums[1] ? 1 : 2, fabs(sums[0] - sums[1])); 
    } else { 
    // Else, just print something 
    for (size_t fn_i = 0; fn_i < FUNCTION_COUNT; fn_i++) { 
     printf("Test %zu took %f clock ticks average.\n", fn_i + 1, sums[fn_i]); 
    } 
    } 

    // Everything went fine! 
    return EXIT_SUCCESS; 
} 

、本当に最速である、m64またはimm64

ところで、私はIntel Core i7 Ivy BridgeとDDR3 RAMを使用しています。私はmacOS High Sierraを実行しています。

EDITret命令を挿入しました。現在はimm64が高速になっています。

+0

どのように正確にテストしましたか?あなたの '_test1'は返されません:' _test2'に落ちます。 (これもまた落ちる)。あなたは一斉にアンロールしましたか? –

+0

test関数の呼び出しごとに 'mach_absolute_time()'を2回呼び出します。少なくともRDTSC命令(21uops、IvBでは27cごとに1つのスループット)を実行しなければならないので、それ以上の時間がかかります。あなたはおそらく、フロントエンドでボトルネックになることはないでしょう。 –

+0

しかし、どうすれば解決できますか? – SpilledMango

答えて

4

テストした実際のループを表示したり、時間の測定方法について何も言わなかったりします。明らかに、コアクロックサイクルではなく、ウォールクロック時間を測定しました(パフォーマンスカウンタを使用)。したがって、測定ノイズの原因には、ターボ/省電力、物理的なコアを別の論理スレッド(i7上)と共有することが含まれます。インテルIvybridgeのオン


movabs rax, 0xDEADBEEFFEEDFACE

  • (または周囲のコードに応じて問題ではない場合があります)、コードサイズの10バイトを取るALU命令です。
  • 任意のALUポート(p0、p1、またはp5)に対して1 uopにデコードします。 (最大スループット= 3 /クロック)
  • uopキャッシュに2つのエントリーをとります(64ビット即値のため)。また、uopキャッシュからの読み取りに2サイクルかかります。 (ループバッファからの実行は、これを含むコードのボトルネックであれば、フロントエンドのスループットにとって大きな利点です)。

mov rax, [RIP + val_ptr]負荷

  • あるロードポートのいずれか1つのUOP(P2またはP3)にデコード7バイト(REX +オペコード+ MODRM + rel32)
  • をとります。 (最大スループット= 1クロックあたり2)
  • は、uopキャッシュ(即時および32または32小規模アドレスオフセットなし)の1エントリに収まります。
  • は、負荷がSkylake上であってもページ境界を越えて分割されている場合、実行速度が大幅に低下します。
  • は初めてキャッシュにミスする可能性があります。

出所:Agner Fog's microarch pdf and instruction tables。 uop-cacheについては、表9.1を参照してください。 タグwikiの他のパフォーマンスリンクも参照してください。


通常、コンパイラはmov r64, imm64の64ビット定数を生成することを選択します。 (関連:What are the best instruction sequences to generate vector constants on the fly?ですが、実際にはno short single-instruction way to get a 64-bit -1が存在するためスカラー整数になることはありません。)

これは一般的に正しい選択ですが、キャッシュ内で定数が暑いと予想される長期実行ループでは、.rodataからロードするのが勝利になる可能性があります。特にmovabs r8, imm64/and rax, r8の代わりにand rax, [constant]のようなことができる場合は特にそうです。

If your 64-bit constant is an address可能であれば、代わりにleaを使用してください。 ASMの小さな配列を考慮するときlea rax, [rel my_symbol]はNASMの文法、AT & T.でlea my_symbol(%rip), %rax


に周囲のコードは、異なるスループットリソースの競合場合は特に、多くの問題になります。

+1

私は 'ret'命令を忘れることができました。 – SpilledMango

+1

@ SpilledMango:あなたはこれらの関数を呼び出すループをテストしていましたか?コール/レットは、これらの命令よりもスループットが制限されています。 (Agner Fogによると、IvBでは2クロックに1回)。あなたは小さな機能のコール/レットでいくつかの奇妙な効果を得ることができます。 Skylakeについては、このケースを参照してください:https://stackoverflow.com/questions/45442458/loop-with-function-call-faster-than-an-empty-loop/45529487ループの中に 'call 'を置くと、平均store-forwarding latencyを低下させることで、実際には' call'をスピードアップしました。したがって、正しいことを測定していることに注意してください。 –

+1

また、ピーターが最後に書いたものを読んだり、最後に読んだり、最後に読んだりしています。小さなコードシーケンスを検討するとき、周囲のコードは、特に異なるスループットリソースのために競争するときに、大変重要です。どの命令や小さなasmイディオムが_faster_なのかを尋ねますが、しばしば文脈に依存することを理解しています。これらの質問の多くは、XYZを実行したい場合は、X、Y、Zを実行する最速の方法を決定し、それらを一緒にストリングして最速のXYZを得るという考えから来ていると思います。これはしばしば真実ではありません。 – BeeOnRope

関連する問題