Преамбула : Я смотрел первый урок этого курса MIT на YouTube , и примерно в 22:20 учитель представляет результаты сравнения производительности того же алгоритма в Java и C. Он сказал, что реализация C в 2 раза быстрее реализации Java, поэтому я попробовал это дома, чтобы увидеть своими глазами, но факт в том, что реализация Java оказалась быстрее, чем * 1069. * один, и мне нужна помощь, чтобы понять, почему.
Реализации проблемы
У меня есть эти две реализации одного и того же алгоритма:
C
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#define n 1024
double A[n][n];
double B[n][n];
double C[n][n];
float tdiff(struct timeval *start,
struct timeval *end)
{
return (end->tv_sec - start->tv_sec) +
1e-6 * (end->tv_usec - start->tv_usec);
}
int main(int argc, const char *argv[])
{
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
A[i][j] = (double)rand() / (double)RAND_MAX;
B[i][j] = (double)rand() / (double)RAND_MAX;
C[i][j] = 0;
}
}
struct timeval start, end;
gettimeofday(&start, NULL);
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
for (int k = 0; k < n; ++k)
{
C[i][j] += A[i][k] * B[k][j];
}
}
}
gettimeofday(&end, NULL);
printf("%0.6f\n", tdiff(&start, &end));
double d = 0;
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
d += C[i][j];
}
}
printf("Fim %0.6f\n", d);
return 0;
}
Java
import java.util.Random;
public class TesteMatrixMultiply{
public final static int n = 1024;
static double[][] A = new double[n][n];
static double[][] B = new double[n][n];
static double[][] C = new double[n][n];
public static void main (String args[])
{
Random r = new Random();
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
A[i][j] = r.nextDouble();
B[i][j] = r.nextDouble();
C[i][j] = 0;
}
}
long start = System.nanoTime();
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
for (int k = 0; k < n; ++k)
{
C[i][j] += A[i][k] * B[k][j];
}
}
}
long stop = System.nanoTime();
double tdiff = (stop - start) * 1e-9;
System.out.println(tdiff);
double d = 0;
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
d+= C[i][j];
}
}
System.out.printf("Fim %.6f\n", d);
}
}
Я запускаю две реализации, используя Linux perf , чтобы я мог собрать некоторую информацию.
C выполнение
16.867058
Fim 268364458.846206
Performance counter stats for './teste-matrix-multiply':
16896,010532 task-clock (msec) # 1,000 CPUs utilized
76 context-switches # 0,004 K/sec
2 cpu-migrations # 0,000 K/sec
6.196 page-faults # 0,367 K/sec
38.943.668.123 cycles # 2,305 GHz (49,98%)
27.050.004.884 instructions # 0,69 insn per cycle (62,51%)
2.194.116.427 branches # 129,860 M/sec (62,51%)
1.323.195 branch-misses # 0,06% of all branches (62,50%)
11.889.108.593 L1-dcache-loads # 703,664 M/sec (62,51%)
1.089.638.099 L1-dcache-load-misses # 9,17% of all L1-dcache hits (62,52%)
1.082.875.343 LLC-loads # 64,091 M/sec (49,99%)
835.764.813 LLC-load-misses # 77,18% of all LL-cache hits (49,99%)
16,898358467 seconds time elapsed
Java выполнение
6.551244991000001
Fim 268384388.815752
Performance counter stats for 'java TesteMatrixMultiply':
6754,506236 task-clock (msec) # 1,013 CPUs utilized
581 context-switches # 0,086 K/sec
27 cpu-migrations # 0,004 K/sec
12.938 page-faults # 0,002 M/sec
23.531.901.353 cycles # 3,484 GHz (49,66%)
19.009.664.164 instructions # 0,81 insn per cycle (62,09%)
3.342.430.354 branches # 494,845 M/sec (62,07%)
3.524.682 branch-misses # 0,11% of all branches (62,32%)
7.752.842.493 L1-dcache-loads # 1147,803 M/sec (62,73%)
2.676.041.100 L1-dcache-load-misses # 34,52% of all L1-dcache hits (62,88%)
1.067.905.566 LLC-loads # 158,103 M/sec (50,34%)
55.152.268 LLC-load-misses # 5,16% of all LL-cache hits (50,00%)
6,669662205 seconds time elapsed
Компиляции
Реализация Java была скомпилирована с:
library: zulu11.35.13-ca-jdk11.0.5-linux_x64
call: javac TesteMatrixMultiply.java
Реализация C была скомпилирована с:
library: clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04
call: clang teste-matrix-multiply.c -o teste-matrix-multiply-clang
Мой процессор
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 142
Model name: Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz
Stepping: 9
CPU MHz: 2100.157
CPU max MHz: 3500,0000
CPU min MHz: 400,0000
BogoMIPS: 5808.00
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 4096K
NUMA node0 CPU(s): 0-3
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single pti ssbd ibrs ibpb stibp tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid mpx rdseed adx smap clflushopt intel_pt xsaveopt xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp md_clear flush_l1d
Понятно, что выполнение C сильно страдает от промахов в кеше, но я не могу понять, почему выполнение Java нет такой же проблемы.
Обновление 1
Я действительно не знал о параметре -O3 раньше, теперь я перекомпилировал его, используя его, и производительность были значительно улучшены, но количество промахов в кэше по-прежнему велико.
Я знаю, что это зависит от моего c оборудования. Но я хотел бы объяснить, почему я получаю эти числа. На какие инструменты или команды диагностики c мне следует обратить внимание? Представьте, что это не мой ноутбук, это рабочий сервер, и я должен понимать, что происходит. Что я должен делать? Оптимизация кода не является вариантом в этом упражнении.