このブログは、株式会社フィックスターズのエンジニアが、あらゆるテーマについて自由に書いているブログです。
NVIDIA 社の開発した Denver という 64bit ARM CPU は、ARM 機械語を最適化する機能を持っています。これをいくらか見てみようと思います
Nexus9 の Android 上 で 64bit ELF を動かしています。コンパイルは、TADPを入れた環境で、
$ /usr/local/cuda-android/android-ndk-r10c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-gcc –sysroot /usr/local/cuda-android/android-ndk-r10c/platforms/android-21/arch-arm64 -fPIE -pie -O2 -g
などのようにしてください。
IPC は、 perf_event の PERF_COUNT_HW_CPU_CYCLES、PERF_COUNT_HW_INSTRUCTIONS から計算しています。
ループに大量の nop を入れます。
static void
nop_func(int iter)
{
for (int i=0; i<100000; i++) {
ITER32(__asm__ __volatile__ ("nop":::"memory"););
}
}
nop- 0: 1.118293, clk=3046048, inst=3406375
nop- 1: 28.883114, clk=117781, inst=3401882
nop- 2: 28.916995, clk=117643, inst=3401882
nop- 3: 28.978823, clk=117392, inst=3401882
nop- 4: 28.978823, clk=117392, inst=3401882
nop- 5: 28.978823, clk=117392, inst=3401882
nop- 6: 28.978823, clk=117392, inst=3401882
nop- 7: 28.978823, clk=117392, inst=3401882
nop- 8: 28.978823, clk=117392, inst=3401882
nop- 9: 28.978823, clk=117392, inst=3401882
nop-10: 28.978823, clk=117392, inst=3401882
最初は IPC1.1ぐらいですが、二回目からは29 ぐらいになっています。
このことから、パフォーマンスカウンタの実行命令数はARM命令単位でカウントしていることと、IPCがスペック上の理論値 7 を超えていることから、nop は消えてしまうことがわかります。
無限にIPC増やしていけそうですが、どこかでリミットがかかっているようで、無限に増えるわけではないようです(組み合わせによっては52ぐらい出るのですがそれ以上確認できず)。
いくらか演算したあとに、レジスタの値を初期化します
static void
int_nop_clear_func(int iter)
{
int val=0;
__asm__ __volatile__ ("mov %[val], 1\n\t"
:[val]"+r"(val));
for (int i=0; i<100000; i++) {
ITER32(__asm__ __volatile__ ("add %[val], %[val], %[val]\n\t"
:[val]"+r"(val)
););
__asm__ __volatile__ ("mov %[val], 0\n\t"
:[val]"+r"(val));
}
}
int_nop_clear- 0: 0.511133, clk=6851221, inst=3501882
int_nop_clear- 1: 0.440890, clk=7954954, inst=3507263
int_nop_clear- 2: 0.669569, clk=5230052, inst=3501882
int_nop_clear- 3: 16.883293, clk=207417, inst=3501882
int_nop_clear- 4: 16.946531, clk=206643, inst=3501882
int_nop_clear- 5: 17.039296, clk=205518, inst=3501882
int_nop_clear- 6: 17.047508, clk=205419, inst=3501882
int_nop_clear- 7: 17.050413, clk=205384, inst=3501882
これも命令が消えているように見えますね。無駄なaddは消えることがわかります。
ただ、ループARM命令の最適化はC言語の最適化よりも難しい処理で、割りこみルーチンがレジスタを読み書きするというのを考えると、割り込みが入る可能性のある箇所では、レジスタ単位でもとのプログラムの値を正しく再現する必要があります。おそらく、そういう理由があるので、プログラムが複雑になると、最適化はされないようです。
例えば、
static void
int_nop_func(int iter)
{
int v1 = 1;
int vsum = 0;
for (int i=0; i<100000; i++) {
ITER32(__asm__ __volatile__ ("mov %[v1], 1\n\t"
"add %[vsum], %[vsum], %[v1]\n\t"
:[v1]"+r"(v1), [vsum]"+r"(vsum));
);
}
data[0][0] = vsum;
}
のように、レジスタに定数を入れる処理をすぐ隣に置くと、命令は削除されるようですが、
static void
int_nop_func(int iter)
{
int v1 = 1;
int vsum = 0;
for (int i=0; i<100000; i++) {
ITER32(__asm__ __volatile__ ("add %[vsum], %[vsum], %[v1]\n\t"
:[v1]"+r"(v1), [vsum]"+r"(vsum));
);
}
data[0][0] = vsum;
}
のように、レジスタの初期化をループ外に出したり、
static void
cond_nop_func(int iter)
{
__asm__ __volatile__("mov w0, 1" ::: "w0");
for (int i=0; i<100000; i++) {
ITER32(__asm__ __volatile__ ("add w0, w0, w0\n\tcmp w0, 4\n\tbeq 1f\n\t1:\n\t":::"w0", "memory"););
__asm__ __volatile__("mov w0, 1" ::: "w0");
}
}
のように、途中に条件分岐を入れたりすると、命令は削除されないようです。
浮動小数演算はかなりシンプルな例でも削除されないようです。
static void
fp_nop_func(int iter)
{
for (int i=0; i<100000; i++) {
ITER32(__asm__ __volatile__ ("fmov s0, 1.0e+0\n\t"
"fadd s0, s0, s0\n\t"
:
:
:"memory", "s0");
);
}
}
は、IPCが3.8ぐらいになります。
ARMv8 はよく知らないのでなんとも言えないですが、FPUは入力値やモード設定によって例外出す可能性があるので、命令を削除する最適化は入っていないのではないかという気がします。
NVIDIA社のblogにあるDenverの紹介を見ると
http://blogs.nvidia.com/blog/2014/08/11/tegra-k1-denver-64-bit-for-android/
JSR, ICU0, ICU1, FP0, FP1, LS0, LS1 が並んでいるように見えます。
名前からすると、ジャンプx1、整数x2、浮動小数x2、ロードストアx2 で 7way だと推測されます
いくらか試した範囲だと、↓ のようにすれば、IPC 5.4 になるようです。
static void
ipc7(int iter)
{
int val=0, val2=0, val3=0, val4=0, val5=0, val6=0, val7 = 0;
float fval=0, fval2=0, fval3=0;
for (int i=0; i<100000; i++) {
int *ptr = (int*)(data_large);
int *ptr2 = (int*)(data_large+1024+16);
ITER32(
__asm__ __volatile__("ldr %w[reg], [%[ptr]], 8\n\t"
"ldr %w[reg2], [%[ptr2]], 8\n\t"
"add %[reg4], %[reg4], %[reg5]\n\t"
"add %[reg5], %[reg5], %[reg5]\n\t"
"add %[reg6], %[reg6], %[reg7]\n\t"
//"add %[reg7], %[reg7], %[reg7]\n\t"
"fadd %s[freg2], %s[freg], %s[freg]\n\t"
"fadd %s[freg3], %s[freg], %s[freg]\n\t"
:[reg]"+r"(val),
[reg2]"+r"(val2),
[reg3]"+r"(val3),
[reg4]"+r"(val4),
[reg5]"+r"(val5),
[reg6]"+r"(val6),
[reg7]"+r"(val7),
[freg]"+w"(fval),
[freg2]"+w"(fval2),
[freg3]"+w"(fval3),
[ptr]"+r"(ptr),
[ptr2]"+r"(ptr2)
);
);
}
}
5.4というのもよくわからない値ですね。あと整数命令は3つ入れたほうがIPCが上がるようです。 nop のところで書いたように消えたARM命令もカウントされてしまうので、何かそういうのが効いてるのかもしれません。
最適化がかかっていない最初の実行ではIPC 1程度になっています。
ipc7- 0: 1.097943, clk=13617237, inst=14950954
ipc7- 1: 5.285204, clk=2827535, inst=14944099
ipc7- 2: 5.139465, clk=2908479, inst=14948025
ipc7- 3: 5.272507, clk=2834344, inst=14944099
ipc7- 4: 5.264390, clk=2838714, inst=14944099
ipc7- 5: 5.277134, clk=2831859, inst=14944099
ipc7- 6: 5.263929, clk=2838963, inst=14944099
一応依存の無いようにARM命令並べていますが、それではIPC上がらないようなので、ARMのハードウェアデコーダは、スループット1命令なのではないかと予想されます
ついでにいくつか試した値を書いておくと、
__asm__ __volatile__("ldr %w[reg], [%[ptr]], 8\n\t"
//"ldr %w[reg2], [%[ptr2]], 8\n\t"
//"add %[reg4], %[reg4], %[reg5]\n\t"
//"add %[reg5], %[reg5], %[reg5]\n\t"
//"add %[reg6], %[reg6], %[reg7]\n\t"
//"add %[reg7], %[reg7], %[reg7]\n\t"
//"fadd %s[freg2], %s[freg], %s[freg]\n\t"
//"fadd %s[freg3], %s[freg], %s[freg]\n\t"
:[reg]"+r"(val),
[reg2]"+r"(val2),
[reg3]"+r"(val3),
[reg4]"+r"(val4),
[reg5]"+r"(val5),
[reg6]"+r"(val6),
[reg7]"+r"(val7),
[freg]"+w"(fval),
[freg2]"+w"(fval2),
[freg3]"+w"(fval3),
[ptr]"+r"(ptr),
[ptr2]"+r"(ptr2)
);
これが IPC8.8。32bit ARMは連続するアドレスを複数レジスタにロードする命令があるので、それに近いものに変換されてるように見えます。
__asm__ __volatile__("ldr %w[reg], [%[ptr]], 8\n\t"
"ldr %w[reg2], [%[ptr2]], 8\n\t"
//"add %[reg4], %[reg4], %[reg5]\n\t"
//"add %[reg5], %[reg5], %[reg5]\n\t"
//"add %[reg6], %[reg6], %[reg7]\n\t"
//"add %[reg7], %[reg7], %[reg7]\n\t"
//"fadd %s[freg2], %s[freg], %s[freg]\n\t"
//"fadd %s[freg3], %s[freg], %s[freg]\n\t"
:[reg]"+r"(val),
[reg2]"+r"(val2),
[reg3]"+r"(val3),
[reg4]"+r"(val4),
[reg5]"+r"(val5),
[reg6]"+r"(val6),
[reg7]"+r"(val7),
[freg]"+w"(fval),
[freg2]"+w"(fval2),
[freg3]"+w"(fval3),
[ptr]"+r"(ptr),
[ptr2]"+r"(ptr2)
);
これが 2.4。 謎の値ですね。
__asm__ __volatile__(//"ldr %w[reg], [%[ptr]], 8\n\t"
//"ldr %w[reg2], [%[ptr2]], 8\n\t"
"add %[reg4], %[reg4], %[reg5]\n\t"
"add %[reg5], %[reg5], %[reg5]\n\t"
"add %[reg6], %[reg6], %[reg7]\n\t"
"add %[reg7], %[reg7], %[reg7]\n\t"
//"fadd %s[freg2], %s[freg], %s[freg]\n\t"
//"fadd %s[freg3], %s[freg], %s[freg]\n\t"
:[reg]"+r"(val),
[reg2]"+r"(val2),
[reg3]"+r"(val3),
[reg4]"+r"(val4),
[reg5]"+r"(val5),
[reg6]"+r"(val6),
[reg7]"+r"(val7),
[freg]"+w"(fval),
[freg2]"+w"(fval2),
[freg3]"+w"(fval3),
[ptr]"+r"(ptr),
[ptr2]"+r"(ptr2));
これが 3.8。 整数加算は4並列できる。
__asm__ __volatile__(//"ldr %w[reg], [%[ptr]], 8\n\t"
//"ldr %w[reg2], [%[ptr2]], 8\n\t"
"and %[reg4], %[reg4], %[reg5]\n\t"
"and %[reg5], %[reg5], %[reg5]\n\t"
"and %[reg6], %[reg6], %[reg7]\n\t"
"and %[reg7], %[reg7], %[reg7]\n\t"
//"fadd %s[freg2], %s[freg], %s[freg]\n\t"
//"fadd %s[freg3], %s[freg], %s[freg]\n\t"
:[reg]"+r"(val),
[reg2]"+r"(val2),
[reg3]"+r"(val3),
[reg4]"+r"(val4),
[reg5]"+r"(val5),
[reg6]"+r"(val6),
[reg7]"+r"(val7),
[freg]"+w"(fval),
[freg2]"+w"(fval2),
[freg3]"+w"(fval3),
[ptr]"+r"(ptr),
[ptr2]"+r"(ptr2)
);
and は4.1。3.8との差は謎ですね。
Denver は、in-order でも高IPCという宣伝がされているので、なんらかの命令スケジューリングがされていると予想されます。実際、以下のようなコードを入れると、
static void
loop_int_dep(int iter)
{
int val=0, val2=0;
for (int j=0; j<128; j++) {
int *ptr = (int*)(data_large+0);
int *ptr2 = (int*)(data_large+128);
if (iter > 8 && iter < 24) {
ptr2 = (int*)(data_large+0);
}
for (int i=0; i<4096; i++) {
ITER32(__asm__ __volatile__("ldr %w[reg], [%[ptr]]\n\t"
"add %w[reg], %w[reg], 1\n\t"
"str %w[reg], [%[ptr]]\n\t"
"ldr %w[reg2], [%[ptr2]]\n\t"
"add %w[reg2], %w[reg2], 1\n\t"
"str %w[reg2], [%[ptr2]]\n\t"
:[reg]"+r"(val),
[reg2]"+r"(val2),
[ptr]"+r"(ptr),
[ptr2]"+r"(ptr2)););
}
}
}
何回か実行すると、IPC 3 ぐらいになるので、スケジューリングが行われていることが確認できます。
上の関数では、計測用に32回関数を実行していて、9〜23回目の処理は、スケジューリングができないようにしています。
loop_int_dep- 0: 2.535232, clk=40134092, inst=101749245
loop_int_dep- 1: 2.922908, clk=34802246, inst=101723746
loop_int_dep- 2: 2.928609, clk=34732553, inst=101718050
loop_int_dep- 3: 2.924591, clk=34782669, inst=101725097
loop_int_dep- 4: 2.928548, clk=34733520, inst=101718787
loop_int_dep- 5: 2.924948, clk=34777964, inst=101723746
loop_int_dep- 6: 2.928742, clk=34730970, inst=101718050
loop_int_dep- 7: 2.925112, clk=34776014, inst=101723746
loop_int_dep- 8: 2.928069, clk=34739210, inst=101718787
loop_int_dep- 9: 0.743494, clk=136840199, inst=101739872
loop_int_dep-10: 0.743998, clk=136745227, inst=101738176
loop_int_dep-11: 0.743323, clk=136873555, inst=101741215
loop_int_dep-12: 0.744145, clk=136720483, inst=101739864
loop_int_dep-13: 0.743707, clk=136801106, inst=101739886
loop_int_dep-14: 0.741004, clk=137332227, inst=101763765
loop_int_dep-15: 1.031812, clk=98597544, inst=101734168
loop_int_dep-16: 2.924615, clk=34780771, inst=101720379
loop_int_dep-17: 2.926805, clk=34755262, inst=101721861
loop_int_dep-18: 2.926108, clk=34762780, inst=101719632
loop_int_dep-19: 2.927046, clk=34752391, inst=101721839
loop_int_dep-20: 2.926114, clk=34762945, inst=101720347
loop_int_dep-21: 2.927109, clk=34751674, inst=101721930
loop_int_dep-22: 2.928033, clk=34739341, inst=101717922
loop_int_dep-23: 2.924022, clk=34789375, inst=101724908
loop_int_dep-24: 2.919629, clk=34841676, inst=101724772
loop_int_dep-25: 2.923048, clk=34800538, inst=101723655
loop_int_dep-26: 2.928477, clk=34734111, inst=101718050
loop_int_dep-27: 2.919913, clk=34839363, inst=101727892
loop_int_dep-28: 2.927462, clk=34746382, inst=101718696
loop_int_dep-29: 2.924561, clk=34782568, inst=101723746
loop_int_dep-30: 2.928483, clk=34734039, inst=101718050
loop_int_dep-31: 2.926565, clk=34758150, inst=101721989
結果は、上のとおりです。
メモリアクセスを投機実行しているはずで、9〜23回目では、これは失敗します。失敗したあと、しばらく0.75ぐらいが続いて、またしばらくすると、3程度に戻ります。
二回目のテストでは、
loop_int_dep- 0: 2.912313, clk=34933380, inst=101736943
loop_int_dep- 1: 2.927798, clk=34742419, inst=101718796
loop_int_dep- 2: 2.923490, clk=34795326, inst=101723786
loop_int_dep- 3: 2.928089, clk=34738689, inst=101717968
loop_int_dep- 4: 2.924932, clk=34778167, inst=101723764
loop_int_dep- 5: 2.925973, clk=34764568, inst=101720184
loop_int_dep- 6: 2.925029, clk=34777027, inst=101723817
loop_int_dep- 7: 2.928099, clk=34738573, inst=101717968
loop_int_dep- 8: 2.926438, clk=34759923, inst=101722748
loop_int_dep- 9: 2.926079, clk=34763370, inst=101720356
loop_int_dep-10: 2.927156, clk=34751120, inst=101721948
loop_int_dep-11: 2.923855, clk=34790476, inst=101722316
loop_int_dep-12: 2.926082, clk=34763844, inst=101721857
loop_int_dep-13: 2.926529, clk=34758025, inst=101720356
loop_int_dep-14: 2.925821, clk=34767442, inst=101723299
loop_int_dep-15: 2.928578, clk=34732874, inst=101717931
loop_int_dep-16: 2.924696, clk=34780929, inst=101723658
loop_int_dep-17: 2.927208, clk=34750477, inst=101721857
loop_int_dep-18: 2.924523, clk=34782311, inst=101721665
loop_int_dep-19: 2.911301, clk=34947141, inst=101741638
loop_int_dep-20: 2.925671, clk=34767933, inst=101719528
loop_int_dep-21: 2.926479, clk=34759153, inst=101721948
loop_int_dep-22: 2.926705, clk=34755930, inst=101720356
loop_int_dep-23: 2.926610, clk=34757608, inst=101721948
loop_int_dep-24: 2.928596, clk=34732705, inst=101718059
loop_int_dep-25: 2.924693, clk=34780980, inst=101723695
loop_int_dep-26: 2.928238, clk=34737208, inst=101718796
loop_int_dep-27: 2.923950, clk=34790300, inst=101725115
loop_int_dep-28: 2.928110, clk=34738469, inst=101718059
loop_int_dep-29: 2.921115, clk=34824739, inst=101727061
loop_int_dep-30: 2.926998, clk=34751931, inst=101718818
loop_int_dep-31: 2.927037, clk=34752549, inst=101721985
このように、投機実行に失敗しているのを感じさせない値になります。よほど特殊な命令を実装しているのでない限り、これをひとつのループで実現することはできないと思うので、
の2バージョンのループを作っているのではないかと思います。
static void
loop_fp_dep(int iter)
{
float reg=0;
for (int j=0; j<128; j++) {
float *ptr = (float*)(fp_data_large);
float *ptr2 = (float*)(fp_data_large+128);
if (iter > 8 && iter < 24) {
ptr2 = (float*)fp_data_large;
}
for (int i=0; i<1024; i++) {
ITER32(__asm__ __volatile__("ldr %s[reg], [%[ptr]]\n\t"
"fadd %s[reg], %s[reg], %s[reg]\n\t"
"str %s[reg], [%[ptr]]\n\t"
"ldr %s[reg], [%[ptr2]]\n\t"
"fadd %s[reg], %s[reg], %s[reg]\n\t"
"str %s[reg], [%[ptr2]]\n\t"
:[reg]"+w"(reg),
[ptr]"+r"(ptr),
[ptr2]"+r"(ptr2));
);
}
}
}
浮動小数で同じような処理を実行した場合の結果が、
loop_fp_dep- 0: 0.769715, clk=33085834, inst=25466651
loop_fp_dep- 1: 1.391458, clk=18281149, inst=25437442
loop_fp_dep- 2: 1.395606, clk=18224979, inst=25434882
loop_fp_dep- 3: 1.396779, clk=18208007, inst=25432559
loop_fp_dep- 4: 1.396975, clk=18206595, inst=25434167
loop_fp_dep- 5: 1.397371, clk=18201888, inst=25434791
loop_fp_dep- 6: 1.397803, clk=18195796, inst=25434145
loop_fp_dep- 7: 1.394573, clk=18238471, inst=25434881
loop_fp_dep- 8: 1.396573, clk=18212372, inst=25434904
loop_fp_dep- 9: 0.138509, clk=183860933, inst=25466446
loop_fp_dep-10: 0.138527, clk=183842089, inst=25467067
loop_fp_dep-11: 0.138512, clk=183870291, inst=25468200
loop_fp_dep-12: 0.138557, clk=183773406, inst=25463143
loop_fp_dep-13: 0.138428, clk=184003031, inst=25471206
loop_fp_dep-14: 0.138396, clk=184148742, inst=25485498
loop_fp_dep-15: 0.138064, clk=184493143, inst=25471895
loop_fp_dep-16: 0.137496, clk=185191576, inst=25463094
loop_fp_dep-17: 0.137469, clk=185245081, inst=25465463
loop_fp_dep-18: 0.137478, clk=185262439, inst=25469551
loop_fp_dep-19: 0.137497, clk=185190397, inst=25463163
loop_fp_dep-20: 0.137492, clk=185223010, inst=25466767
loop_fp_dep-21: 0.137511, clk=185183573, inst=25464787
loop_fp_dep-22: 0.137512, clk=185169910, inst=25463094
loop_fp_dep-23: 0.137504, clk=185215863, inst=25467866
loop_fp_dep-24: 0.431694, clk=58939348, inst=25443777
loop_fp_dep-25: 0.431779, clk=58918464, inst=25439769
loop_fp_dep-26: 0.431835, clk=58914695, inst=25441424
loop_fp_dep-27: 0.431673, clk=58942253, inst=25443777
loop_fp_dep-28: 0.429710, clk=59250775, inst=25460679
loop_fp_dep-29: 0.431422, clk=58982682, inst=25446399
loop_fp_dep-30: 0.431863, clk=58903057, inst=25438062
loop_fp_dep-31: 0.431356, clk=58993471, inst=25447198
このようになります。
整数では、投機実行失敗後、しばらくするとIPC3 に戻りましたが、浮動小数では、1回遅くなると、24回目以降も遅くなったままなので、投機実行をやめているように見えます。
static void
unroll_func(int iter)
{
int v0=0, v1=1, v2=3;
int *p = data_large;
for (int i=0; i<100000; i++) {
__asm__ __volatile__("ldr %w[v0], [%[ptr]]\n\t"
"add %w[v0], %w[v0], %w[v1]\n\t"
"add %w[v0], %w[v0], %w[v2]\n\t"
"str %w[v0], [%[ptr]], 4\n\t"
:[v0]"+r"(v0),
[v1]"+r"(v1),
[v2]"+r"(v2),
[ptr]"+r"(p));
}
}
ループ中では依存があり、ループのイテレーション間では依存が無いという処理です。
これはIPC3.1程度になるので、アンロールかそれに相当する最適化をしていると予想されます。
関数ポインタを付けかえて間接分岐します。
void ret(){
noarg_func_ptr = ret2;
return ;
}
void ret2(){
noarg_func_ptr = ret3;
return ;
}
void ret3(){
noarg_func_ptr = ret4;
return ;
}
void ret4(){
noarg_func_ptr = ret5;
return ;
}
void ret5(){
noarg_func_ptr = ret6;
return ;
}
void ret6(){
noarg_func_ptr = ret7;
return ;
}
void ret7(){
noarg_func_ptr = ret8;
return ;
}
void ret8(){
noarg_func_ptr = ret9;
return ;
}
void ret9(){
noarg_func_ptr = ret;
return ;
}
static void
indirect_call(int iter)
{
int val = 0;
for (int i=0; i<100000; i++) {
ITER32(noarg_func_ptr(););
}
}
使う関数の数(noarg_func_ptr = ret するまでの数)を変えると、
関数の数 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
IPC | 2.0 | 1.2 | 0.7 | 1.1 | 0.6 | 0.7 | 0.6 | 2.0 | 0.5 |
のようになります。最適化の結果なのか、BTBのway構成の結果なのかはちょっとよくわからないですね…
あまり派手なことはしていないですが、地道な作業はひととおりやっているという印象を受けます。
コンピュータビジョンセミナーvol.2 開催のお知らせ - ニュース一覧 - 株式会社フィックスターズ in Realizing Self-Driving Cars with General-Purpose Processors 日本語版
[…] バージョンアップに伴い、オンラインセミナーを開催します。 本セミナーでは、...
【Docker】NVIDIA SDK Managerでエラー無く環境構築する【Jetson】 | マサキノート in NVIDIA SDK Manager on Dockerで快適なJetsonライフ
[…] 参考:https://proc-cpuinfo.fixstars.com/2019/06/nvidia-sdk-manager-on-docker/ […]...
Windowsカーネルドライバを自作してWinDbgで解析してみる① - かえるのほんだな in Windowsデバイスドライバの基本動作を確認する (1)
[…] 参考:Windowsデバイスドライバの基本動作を確認する (1) - Fixstars Tech Blog /proc/cpuinfo ...
2021年版G検定チートシート | エビワークス in ニューラルネットの共通フォーマット対決! NNEF vs ONNX
[…] ONNX(オニキス):Open Neural Network Exchange formatフレームワーク間のモデル変換ツー...
YOSHIFUJI Naoki in CUDAデバイスメモリもスマートポインタで管理したい
ありがとうございます。別に型にこだわる必要がないので、ユニバーサル参照を受けるよ...