Анализ производительности того же алгоритма в Java и C - PullRequest
3 голосов
/ 19 июня 2020

Преамбула : Я смотрел первый урок этого курса 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 мне следует обратить внимание? Представьте, что это не мой ноутбук, это рабочий сервер, и я должен понимать, что происходит. Что я должен делать? Оптимизация кода не является вариантом в этом упражнении.

Ответы [ 2 ]

1 голос
/ 19 июня 2020

Результаты зависят от нескольких переменных, таких как версия java или уровень оптимизации компилятора C.

Я выполнил ваши тесты производительности с помощью GitHub Actions, используя разные версии java и разные оптимизации уровни здесь .

Однако я в настоящее время не заставлял его работать с использованием OpenJ9 (который, как говорят, быстрее, чем JVM горячей точки).

Мои результаты:

Java

8

real    0m2.283s
user    0m2.263s
sys 0m0.028s

11

real    0m5.373s
user    0m5.376s
sys 0m0.064s

14

real    0m4.570s
user    0m4.552s
sys 0m0.040s

C

-O0

real    0m10.332s
user    0m10.319s
sys 0m0.008s

-O1

real    0m3.747s
user    0m3.734s
sys 0m0.012s

-O2

real    0m3.718s
user    0m3.709s
sys 0m0.008s

-O3

real    0m1.964s
user    0m1.951s
sys 0m0.012s

Заключение

Как видите, Java 8 кажется самым быстрым в моем тесте и находится между C уровнем оптимизации 2 и 3.

Java 11 и Java 14 находятся между C уровнями оптимизации 0 и 1, хотя я думал, что более новые версии java будут быстрее.

1 голос
/ 19 июня 2020

Я не могу воспроизвести ваши результаты. В своей системе я получаю результаты, похожие на видео.

Вы уверены, что компилируете код C с оптимизацией (например, -O3)?


Java вывод:

17.902893012
Fim 268642903.459048

Без оптимизации вывод C (из gcc):

22.350832
Fim 268364458.846206

Вывод кода C (с -O3):

5.077404
Fim 268364458.846206

Однако clang в моей системе немного медленнее, чем gcc (примерно в 2 раза медленнее) ...

Без оптимизации (из clang):

19.541418
Fim 268364458.846206

С -O3 (от clang):

10.505201
Fim 268364458.846206

Моя система linux, fedora 29 (uname -a):

Linux myhost 5.3.11-100.fc29.x86_64 #1 SMP Tue Nov 12 20:41:25 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

g cc версия:

gcc (GCC) 8.3.1 20190223 (Red Hat 8.3.1-2)

версия clang:

clang version 7.0.1 (Fedora 7.0.1-6.fc29)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

javac версия:

javac 1.8.0_232
* 1044 Версия *java:
openjdk version "1.8.0_232"
OpenJDK Runtime Environment (build 1.8.0_232-b09)
OpenJDK 64-Bit Server VM (build 25.232-b09, mixed mode)

Я использую 4-ядерный linux компьютер с гиперпоточностью, поэтому 8 эффективных ядер. Вот первая часть моего /proc/cpuinfo:

processor   : 0
vendor_id   : GenuineIntel
cpu family  : 6
model       : 26
model name  : Intel(R) Core(TM) i7 CPU         920  @ 2.67GHz
stepping    : 5
microcode   : 0x1d
cpu MHz     : 2786.179
cache size  : 8192 KB
physical id : 0
siblings    : 8
core id     : 0
cpu cores   : 4
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 11
wp      : yes
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 ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm sse4_1 sse4_2 popcnt lahf_lm pti ssbd ibrs ibpb stibp tpr_shadow vnmi flexpriority ept vpid dtherm ida flush_l1d
bugs        : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit
bogomips    : 5307.00
clflush size    : 64
cache_alignment : 64
address sizes   : 36 bits physical, 48 bits virtual
power management:
...