GraalVMのnative-imageを試す

https://www.graalvm.org/docs/getting-started/

こちらの手順に沿ってダウンロードして、エイリアスの設定

alias java8=~/graalvm-ce-19.2.0.1/Contents/Home/bin/java
alias javac8=~/graalvm-ce-19.2.0.1/Contents/Home/bin/javac

以下のソースで実験

// VectorTest3.java
public class VectorTest3 {
    private final static int NUM = 100 * 1000 * 1000;

    private static float dotProduct(float[] vec_a, float[] vec_b) {
        float sum = 0;
        for (int i = 0; i < vec_a.length; i++) {
            sum += vec_a[i] * vec_b[i];
        }
        return sum;
    }

    private static void bench1() {
        float[] vec_a = new float[NUM];
        float[] vec_b = new float[NUM];
        for (int i = 0;i < NUM;i++) {
            vec_a[i] = (float)Math.random();
            vec_b[i] = (float)Math.random();
        }
        long start = System.currentTimeMillis();
        float sum = dotProduct(vec_a, vec_b);
        System.out.format("bench1 - %d ms\n", (System.currentTimeMillis() - start));
    }
    public static void main(String[] args) {
        for(int i = 0;i < 10;i++) {
            bench1();
        }
    }
}

前回のpanamaビルドで実行

$ javac14 src/main/java/VectorTest3.java \
                                          -d out/
$ java14 -cp out/ VectorTest3
bench1 - 137 ms
bench1 - 124 ms
bench1 - 130 ms
bench1 - 112 ms
bench1 - 121 ms
bench1 - 130 ms
bench1 - 120 ms
bench1 - 120 ms
bench1 - 122 ms
bench1 - 117 ms

最速で117msです。 GraalVMの方では

$ javac8 src/main/java/VectorTest3.java \
                                          -d out/
$ java8 -cp out/ VectorTest3
bench1 - 131 ms
bench1 - #
# A fatal error has been detected by the Java Runtime Environment:
#
#  Internal Error (deoptimization.cpp:808), pid=62160, tid=0x0000000000001603
#  fatal error: java/lang/Long$LongCache must be initialized

クラッシュ...

native-imageをします。

# インストール
$ ~/graalvm-ce-19.2.0.1/Contents/Home/bin/gu install native-image

$  ~/graalvm-ce-19.2.0.1/Contents/Home/bin/native-image -cp out/ VectorTest3
Build on Server(pid: 44023, port: 50514)
[vectortest3:44023]    classlist:     143.75 ms
[vectortest3:44023]        (cap):   2,228.31 ms
[vectortest3:44023]        setup:   3,358.35 ms
[vectortest3:44023]   (typeflow):   2,793.14 ms
[vectortest3:44023]    (objects):   2,172.78 ms
[vectortest3:44023]   (features):     170.70 ms
[vectortest3:44023]     analysis:   5,226.55 ms
[vectortest3:44023]     (clinit):      96.69 ms
[vectortest3:44023]     universe:     324.64 ms
[vectortest3:44023]      (parse):     432.65 ms
[vectortest3:44023]     (inline):     969.11 ms
[vectortest3:44023]    (compile):   4,320.70 ms
[vectortest3:44023]      compile:   6,017.41 ms
[vectortest3:44023]        image:     490.40 ms
[vectortest3:44023]        write:     184.50 ms
[vectortest3:44023]      [total]:  15,869.97 ms

$ ls -la vectortest3
-rwxr-xr-x  1 tak  staff  4496880 Oct  5 11:00 vectortest3

約4MBの実行ファイルが生成されました。 実行します。

$ ./vectortest3
bench1 - 127 ms
bench1 - 130 ms
bench1 - 125 ms
bench1 - 126 ms
bench1 - 126 ms
bench1 - 127 ms
bench1 - 136 ms
bench1 - 126 ms
bench1 - 129 ms
bench1 - 129 ms

なんか遅くなっている気がします。

$ otool -L vectortest3
vectortest3:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.0.0)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1670.10.0)
    /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)

静的リンクはlibzが必要そうです。

objdump -d vectortest3  | grep vfmadd

vfmaddでgrepしましたが特に使われていないのでベクトルができていない模様

Java Vector API を試す

https://nowokay.hatenablog.com/entry/2019/09/05/015537

こちらの記事を参考にビルドします。

CのコードとJavaのコードを比較していきます。

# vector.h
float dotProduct512(float* vec1, float* vec2, int num);
float dotProduct256(float* vec1, float* vec2, int num);
float dotProduct(float* vec1, float* vec2, int num);
#vector.c
#include "vector.h"
#include <immintrin.h>

float dotProduct512(float* vec1, float* vec2, int num)
{
    __m512 avx_sum = _mm512_setzero_ps();
    int limit = num - num % 16;
    for (int i = 0;i < limit;i += 16) {
        const __m512 a512 = _mm512_loadu_ps((float*)&vec1[i]);
        const __m512 b512 = _mm512_loadu_ps((float*)&vec2[i]);
        avx_sum = _mm512_fmadd_ps(a512, b512, avx_sum);
    }

    float __attribute__((aligned(32))) out[16] = {};
    _mm512_storeu_ps(out, avx_sum);
    float sum = 0;
    for (int i = 0;i < 16;i++) {
        sum += out[i];
    }
    for (int i = limit;i < num;i++) {
        sum += vec1[i] * vec2[i];
    }
    return sum;
}

float dotProduct256(float* vec1, float* vec2, int num)
{
    __m256 avx_sum = _mm256_setzero_ps();
    int limit = num - num % 8;
    for (int i = 0;i < limit;i += 8) {
        const __m256 a256 = _mm256_loadu_ps((float*)&vec1[i]);
        const __m256 b256 = _mm256_loadu_ps((float*)&vec2[i]);
        avx_sum = _mm256_fmadd_ps(a256, b256, avx_sum);
    }

    float __attribute__((aligned(32))) out[16] = {};
    _mm256_store_ps(out, avx_sum);
    float sum = 0;
    for (int i = 0;i < 8;i++) {
        sum += out[i];
    }
    for (int i = limit;i < num;i++) {
        sum += vec1[i] * vec2[i];
    }
    return sum;
}

float dotProduct(float* vec1, float* vec2, int num)
{
    float sum = 0;
    for (int i = 0;i < num;i++) {
        sum += vec1[i] * vec2[i];
    }
    return sum;
}
# vec_test.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include "vector.h"

#define NUM 100 * 1000 * 1000

unsigned long getMicroSec()
{
    struct timespec time1;
    clock_gettime(CLOCK_REALTIME,&time1);

    unsigned long micros = time1.tv_sec * 1000000;
    micros += time1.tv_nsec / 1000;
    return micros;
}

void bench1()
{

    float *vec_a, *vec_b;
    vec_a = (float*)malloc(sizeof(float) * NUM);
    vec_b = (float*)malloc(sizeof(float) * NUM);

    for(int i = 0;i < NUM;i++) {
        vec_a[i] = ((float)rand()) / NUM;
        vec_b[i] = ((float)rand()) / NUM;
    }

    unsigned long start = getMicroSec();
    float sum = dotProduct(vec_a, vec_b, NUM);
    printf("bench1 - %lu ms\n", (getMicroSec() - start) / 1000);

    free(vec_a);
    free(vec_b);
}

void bench2()
{

    float *vec_a, *vec_b;
    vec_a = (float*)malloc(sizeof(float) * NUM);
    vec_b = (float*)malloc(sizeof(float) * NUM);

    for(int i = 0;i < NUM;i++) {
        vec_a[i] = ((float)rand()) / NUM;
        vec_b[i] = ((float)rand()) / NUM;
    }
    unsigned long start = getMicroSec();

    float sum = dotProduct256(vec_a, vec_b, NUM);
    printf("bench2 - %lu ms\n", (getMicroSec() - start) / 1000);

    free(vec_a);
    free(vec_b);
}

int main(void)
{
    bench1();
    bench2();
    return 0;
}

以下のコマンドでビルドします。

gcc -O2 -mavx512f vec_test.c  -o vec_test vector
gcc -O2 vec_test.c  -o vec_test vector

実行結果です。

 $ ./vec_test
bench1 - 135 ms
bench2 - 52 ms

FMA命令を使用した方が早くなります。 次にJavaです。

// VectorTest2.java
import jdk.incubator.vector.*;

public class VectorTest2 {
    private final static int NUM = 100 * 1000 * 1000;

    private static float dotProduct256(float[] vec_a, float[] vec_b) {
        var SP = FloatVector.SPECIES_256;

        int limit = vec_a.length - vec_a.length % 8;
        var fv_sum = FloatVector.fromValues(SP, 0, 0, 0, 0, 0, 0, 0, 0);
        for (int i = 0; i < limit; i += 8) {
            var fv_a = FloatVector.fromArray(SP, vec_a, i);
            var fv_b = FloatVector.fromArray(SP, vec_b, i);
            fv_sum = fv_a.fma(fv_b, fv_sum);
        }

        float[] outArray = new float[8];
        fv_sum.intoArray(outArray, 0);

        float sum = 0;
        for (float f: outArray) {
            sum += f;
        }

        for (int i = limit; i < vec_a.length; i += 1) {
            sum += vec_a[i] * vec_b[i];
        }
        return sum;
    }
    private static float dotProduct(float[] vec_a, float[] vec_b) {
        float sum = 0;
        for (int i = 0; i < vec_a.length; i++) {
            sum += vec_a[i] * vec_b[i];
        }
        return sum;
    }

    private static void bench1() {
        float[] vec_a = new float[NUM];
        float[] vec_b = new float[NUM];
        for (int i = 0;i < NUM;i++) {
            vec_a[i] = (float)Math.random();
            vec_b[i] = (float)Math.random();
        }
        long start = System.currentTimeMillis();
        float sum = dotProduct(vec_a, vec_b);
        System.out.format("bench1 - %d ms\n", (System.currentTimeMillis() - start));
    }
    private static void bench2() {
        float[] vec_a = new float[NUM];
        float[] vec_b = new float[NUM];
        for (int i = 0;i < NUM;i++) {
            vec_a[i] = (float)Math.random();
            vec_b[i] = (float)Math.random();
        }
        long start = System.currentTimeMillis();
        float sum = dotProduct256(vec_a, vec_b);
        System.out.format("bench2 - %d ms\n", (System.currentTimeMillis() - start));
    }
    public static void main(String[] args) {
        for(int i = 0;i < 20;i++) {
            bench1();
        }
        for(int i = 0;i < 20;i++) {
            bench2();
        }
    }
}

ビルドして実行します。

$ javac14 src/main/java/VectorTest2.java \
                    --add-modules jdk.incubator.vector \
                    -d out/
$ java14 -cp out/ VectorTest2
bench1 - 114 ms
bench1 - 127 ms
bench1 - 116 ms
bench1 - 128 ms
bench1 - 108 ms
bench1 - 140 ms
bench1 - 117 ms
bench1 - 114 ms
bench1 - 112 ms
bench1 - 109 ms
bench1 - 144 ms
bench1 - 120 ms
bench1 - 120 ms
bench1 - 110 ms
bench1 - 117 ms
bench1 - 107 ms
bench1 - 107 ms
bench1 - 110 ms
bench1 - 107 ms
bench1 - 109 ms
bench2 - 447 ms
bench2 - 125 ms
bench2 - 163 ms
bench2 - 110 ms
bench2 - 117 ms
bench2 - 117 ms
bench2 - 117 ms
bench2 - 116 ms
bench2 - 123 ms
bench2 - 116 ms
bench2 - 129 ms
bench2 - 116 ms
bench2 - 116 ms
bench2 - 116 ms
bench2 - 117 ms
bench2 - 115 ms
bench2 - 117 ms
bench2 - 116 ms
bench2 - 118 ms
bench2 - 116 ms

VectorAPIを使用しない場合107msが最速ですが、使用した場合116msが最速です。 なぜ使用すると遅くなるのかは不明ですが、まだまだ最適化が必要なのでしょう。


Intel公式資料

Java Doc

AVX512をECで試す

今回はEC2のc5.largeインスタンスを使います。

CPU情報は

$ cat /proc/cpuinfo
processor   : 0
vendor_id   : GenuineIntel
cpu family  : 6
model       : 85
model name  : Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz
stepping    : 4
microcode   : 0x200005e
cpu MHz     : 3408.548
cache size  : 25344 KB
physical id : 0
siblings    : 2
core id     : 0
cpu cores   : 1
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 13
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves ida arat pku ospke
bugs        : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds
bogomips    : 5999.99
clflush size    : 64
cache_alignment : 64
address sizes   : 46 bits physical, 48 bits virtual
power management:

processor   : 1
vendor_id   : GenuineIntel
cpu family  : 6
model       : 85
model name  : Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz
stepping    : 4
microcode   : 0x200005e
cpu MHz     : 3411.384
cache size  : 25344 KB
physical id : 0
siblings    : 2
core id     : 0
cpu cores   : 1
apicid      : 1
initial apicid  : 1
fpu     : yes
fpu_exception   : yes
cpuid level : 13
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves ida arat pku ospke
bugs        : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds
bogomips    : 5999.99
clflush size    : 64
cache_alignment : 64
address sizes   : 46 bits physical, 48 bits virtual
power management:

c5の上位インスタンスではIntel DL Boostに対応していますが、下位インスタンスでは対応していません。

f:id:taku-woohar:20190922175408p:plain
Intel DL Boost対応表
2019/7/24のIntel AIのブログです。

実行のコードです。

vector.h

float sumProduct(float* vec1, float* vec2, int num);

vector.c

#include "vector.h"
#include <immintrin.h>

float sumProduct(float* vec1, float* vec2, int num)
{
    __m512 avx_sum = _mm512_setzero_ps();
    for (int i = 0;i < num;i += 16) {
        const __m512 a512 = _mm512_loadu_ps((double*)&vec1[i]);
        const __m512 b512 = _mm512_loadu_ps((double*)&vec2[i]);
        avx_sum = _mm512_fmadd_ps(a512, b512, avx_sum);
    }

    float __attribute__((aligned(32))) out[16] = {};
    _mm512_storeu_ps(out, avx_sum);
    float sum = 0;
    for (int i = 0;i < 16;i++) {
        sum += out[i];
    }
    return sum;
}

test.c

#include<stdio.h>
#include "vector.h"

int main(void)
{
    float a[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
    float b[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
    printf("%f\n", sumProduct(a, b, 16));
    return 0;
}

以下のコマンドでビルドします。

gcc -O2 -mavx512f vector.c -c  -o vector
gcc -O0 -mavx512f test.c -o test vector
./test
408.000000

参考サイト

https://colfaxresearch.com/knl-avx512/

V言語 0.1.19/0.1.20 リリース

0.1.20

https://github.com/vlang/v/releases/tag/0.1.20

  • JavaScriptエンジンで実行をサポート
  • Android Termux上での実行をサポート

0.1.19

https://github.com/vlang/v/releases/tag/0.1.19

  • 新しいMySQLモジュール
  • ggモジュールがUnicode描画をサポート
  • GitHubがVをサポート
  • v build moduleでのモジュール生成機能

 

再度AV1を含めたエンコード品質の比較

前回の記事でPSNRの比較をしたのですが、指定したビットレートに対し実際のビットレートが乖離していることがわかったので

今回は極力して通りになる様調整しました。NVEncは乖離が激しかった為計測していません。

またAV1も使用しています。

 

環境

Windows 10 / Core i7 9750H / Geforce GTX 1660 Ti

 

コーデック 100k 200k 300k 400k 500k 1m 3m 5m 10m
h264 27.87 29.30 30.55 31.90 33.25 37.06 41.51 43.24 45.18
hevc 32.92 33.68 37.53 38.58 39.35 41.60 44.30 45.28 46.45
vp9 34.46 36.77 38.23 39.18 39.94 42.04 44.43 45.36 46.63
libaom-av1 36.68 39.15 40.47            

 

f:id:taku-woohar:20190909235014p:plain

PSNR

hevcはh264を全てのビットレートでスコアが高いです。vp9は低ビットレートでhevcを上まっています。

av1も良さそうな感じですが、エンコード時間がとても長いので途中で計測を諦めました。

次世代のコーデックはhevcかvp9ということになるのでしょうか。

動画コーデックの画質の比較

ffmpegを使用しPSNRの値を比較しました。

 

環境

Windows 10 / Core i7 9750H / Geforce GTX 1660 Ti

 

 エンコードオプションは以下のように設定しました。

ffmpeg -y -i input.mp4 -c:v libx264 -b:v 100k out_libx264.mp4
ffmpeg -y -i input.mp4 -c:v libx265 -b:v 100k out_libx265.mp4
ffmpeg -y -i input.mp4 -c:v vp9 -b:v 100k out_vp9.mp4
ffmpeg -y -i input.mp4 -c:v h264_nvenc -b:v 100k out_h264_nvenc.mp4
ffmpeg -y -i input.mp4 -c:v hevc_nvenc -b:v 100k out_hevc_nvenc.mp4

PSNRは以下の様にして計測しました。

ffmpeg -i input -i out_.mp4 -lavfi psnr="stats_file=psnr.log" -f null  -

元の動画は1920 x 1080 29.97Pで同一です。

コーデック FPS 100k 300k 500k 1m 3m 5m 10m
libx264 42~102 28.83 31.50 33.84 37.19 41.60 43.29 45.14
libx265 12~52 32.93 37.53 39.35 41.60 44.31 45.29 46.46
vp9 4.2~11 34.83 37.85 39.39 41.32 43.68 44.67 45.97
h264_nvenc 173~175 31.22 31.29 32.71 36.85 40.89 42.67 44.87
hevc_nvenc 124~126 34.93 35.07 37.67 39.91 42.95 44.21 45.80

f:id:taku-woohar:20190906002620p:plain

PSNR

PSNR上ではhevcはh.264の2倍ほどの圧縮率を誇る様です。

今回Geforceが使用できた為GPUエンコードを使用しましたが、速度は圧倒的です。

大きな問題さえなければ積極的に使っていきたいです。