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エンコードを使用しましたが、速度は圧倒的です。

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

 

oidn - ノイズ削除ライブラリを試す

ノイズを削除するライブラリを使用してみたので、さらっと紹介します。

Intel® Open Image Denoise

ビルドします。

git clone --recursive https://github.com/OpenImageDenoise/oidn.git
mkdir oidn/build
cd oidn/build
cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang ..
ccmake .. # cを押してgで完了
make

ビルドが完了したらサンプルを実行します。 レイトレーシングによって生成した画像を使用してみます。

f:id:taku-woohar:20190902211610j:plain
ノイズまみれの画像

この画像をpfm形式(リトルエンディアン)に変換します。 変換にはImageMagickを使用します。

convert image.png -endian lsb PFM:image.pfm

この画像に対して処理をかけてみます。

./denoise -ldr image.pfm  -o output.pfm

f:id:taku-woohar:20190902211840j:plain
デノイズ後

目に見えるノイズは消えています。ただし解像度は落ちています。 色再現性もだいぶ良い感じです。

レイトレーシングとの相性がだいぶ良さそうなので、後処理に使えそうです。