iMacでいろいろ動画エンコード

環境

素材

以下の3つの素材を1920x1080 29.97P に変換する速度を測ります。

  • sony_xavcs_30p.MP4
    • 1920 × 1080 29.97fps
    • 50Mbps
    • Sony RX100M4 X-AVCS
    • 11175F
    • 6分13秒
  • canon_uhd.MP4
    • 3840x2160 29.97fps
    • 120Mbps
    • Canon PowerShot G7 X Mark III
    • 1423F
    • 47秒
  • gh5_422_uhd.MP4
    • 3840x2160 29.97fps
    • 150Mbps
    • LUMIX GH5
    • 1770F
    • 59秒

FFmpeg

FFmpegはバージョン4.2.2を使用します。

Input Codec FPS
sony_xavcs_30p.MP4 libx264 31
h264_videotoolbox 202
libx265 11
canon_uhd.MP4 libx264 11
h264_videotoolbox 53
libx265 4.68
gh5_422_uhd.MP4 libx264 11
h264_videotoolbox 42
libx265 5.0

h264_videotoolboxはmacOSでハードウェアエンコード出来るオプションですが、かなり早いです。

ffmpeg -y -i sony_xavcs_30p.MP4 -c:v libx264 -b:v 5000k fhd2fhd_x264_1.mp4
ffmpeg -y -i sony_xavcs_30p.MP4 -c:v h264_videotoolbox -b:v 5000k fhd2fhd_toolbox_h264_1.mp4
ffmpeg -y -i sony_xavcs_30p.MP4 -c:v libx265 -b:v 5000k fhd2fhd_x265_1.mp4


ffmpeg -y -i canon_uhd.MP4 -c:v libx264 -b:v 5000k uhd2fhd_x264_1.mp4
ffmpeg -y -i canon_uhd.MP4 -c:v h264_videotoolbox -b:v 5000k uhd2fhd_toolbox_h264_1.mp4
ffmpeg -y -i canon_uhd.MP4 -c:v libx265 -b:v 5000k uhd2fhd_h265_1.mp4


ffmpeg -y -i gh5_422_uhd.MP4 -c:v libx264 -b:v 5000k -pix_fmt yuv420p uhd422_to_fhd_x264_1.mp4
ffmpeg -y -i gh5_422_uhd.MP4 -c:v h264_videotoolbox -b:v 5000k  -pix_fmt yuv420p uhd422_to_fhd_toolbox_h264_1.mp4
ffmpeg -y -i gh5_422_uhd.MP4 -c:v libx265 -b:v 5000k -pix_fmt yuv420p uhd422_to_fhd_h265_1.mp4

Adobe Media Encoder編

Input Codec Min:Sec FPS
sony_xavcs_30p.MP4 H.264 1:19 141
HEVC(H.265) 4:18 43
canon_uhd.MP4 H.264 1:53 12
HEVC(H.265) 2:02 11
gh5_422_uhd.MP4 H.264 2:31 11
HEVC(H.265) 2:43 10

上記はAdobeのMedia Encoder 14.1 でのエンコード結果です。H.264はMetal ハードウェアエンコード、H.265はCPUでのエンコードになります。 気になる点はH.265はFFmpegの3倍ほどの速度が出ています。Finder情でもQuickTimeが再生可能な形式なので、違うエンコード結果になっている模様です。

FHDのエンコードは、エンコーダの性能が速度を決める模様です。 UHDはエンコーダに依らず、デコード処理がボトルネックになっているかもしれません。

Prometheus始めてみる

以下のVagrantファイルでイメージを作ります。

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

  config.vm.box = "centos/8"
  # https://github.com/dotless-de/vagrant-vbguest/issues/367
  config.vm.box_url = "http://cloud.centos.org/centos/8/x86_64/images/CentOS-8-Vagrant-8.1.1911-20200113.3.x86_64.vagrant-virtualbox.box"

  config.vm.network "private_network", ip: "192.168.33.30"

  config.vm.provision "shell", inline: <<-SHELL
    mv /etc/localtime /etc/localtime.bak
    ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
    sed -i "/SELINUX/s/enforcing/disabled/g" /etc/selinux/config

    yum install -y java-11-openjdk
  SHELL


  # config.vm.synced_folder "/Users/tak/Documents/program/vagrant/prometheus_vagrant", "/mnt/shared"
end

イメージが出来たらこちらの記事を参考にPrometheusをインストールします。

CentOS8にPrometheusをインストールする

インストールに成功すると以下のURLで状況が見れるようになります。

http://192.168.33.30:9090/graph

次に下のJavaがExporterになります。メモリの状況を通知するプロセスです。

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.net.InetSocketAddress;

public class ExporterTest1 {
    private static final int EXPOSE_PORT = 9092;
    public static void main(String[] args) throws IOException {
        var server = HttpServer.create(new InetSocketAddress(EXPOSE_PORT), 0);
        var context = server.createContext("/");
        context.setHandler(ExporterTest1::handleRequest);
        System.out.println("started at " + EXPOSE_PORT);
        server.start();
    }

    private static void handleRequest(HttpExchange exchange) throws IOException {
        var body = "";
        Runtime runtime = Runtime.getRuntime();
        body += "java_runtime_free_memory\t" + runtime.freeMemory() + "\n";
        var response = body.getBytes();
        exchange.sendResponseHeaders(200, response.length);
        var output = exchange.getResponseBody();
        output.write(response);
        output.close();
    }
}

次は設定ファイルを変更します。

$ sudo vi /usr/prometheus/prometheus.yml

+  - job_name: 'exporter_test'
+    static_configs:
+    - targets: ['localhost:9092']

$ sudo systemctl restart prometheus

Prometheusの上部から Status->Targetをみると

f:id:taku-woohar:20200430220158p:plain
exportの表示
この様に認識されました。 TOPに戻って java_runtime_free_memory -> execute -> grapthでグラフが表示されればOKです。
f:id:taku-woohar:20200430220325p:plain
グラフ

MinikubeでKubernetesを試す

まずは Vagrant でイメージを作成します。

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-19.10"
  config.vm.provider "virtualbox" do |vb|
    vb.cpus = "2"
  end
  config.vm.network "private_network", ip: "192.168.33.100"

  config.vm.provision "shell", inline: <<-SHELL
    curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.9.0/bin/linux/amd64/kubectl
    chmod +x ./kubectl
    mv ./kubectl /usr/local/bin/kubectl

    curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.24.1/minikube-linux-amd64
    chmod +x minikube
    mv minikube /usr/local/bin/

    minikube version
    kubectl version

    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
    sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
    apt-get install docker-ce docker-ce-cli containerd.io -y

    ufw disable
  SHELL
end

起動します。

vagrant up
vagrant ssh

イメージ内で起動するプロセスを作ります。

git clone https://github.com/kubernetes-up-and-running/kuard.git
cd kuard
sudo make
sed -ie 's/FROM ARG_FROM/FROM alpine/' Dockerfile.kuard
sed -ie 's/ARG_FAKEVER\/ARG_ARCH/blue\/amd64/' Dockerfile.kuard
sudo docker build -t kuard-run:1 . -f Dockerfile.kuard
sudo docker run -d --name kuard  -p 8080:8080 kuard-run:1

ブラウザで http://192.168.33.100:8080/ を開いてみます。

f:id:taku-woohar:20200222211155p:plain
KUAR Demo

多分このような画面が出ると思います。

sudo docker stop kuard

で停止します。

Kubernetesを使います。

export CHANGE_MINIKUBE_NONE_USER=true
sudo -E minikube start --vm-driver=none

まず状態を確認します。

$ minikube status
minikube: Running
cluster: Running
kubectl: Correctly Configured: pointing to minikube-vm at 127.0.0.1

$ kubectl get node
NAME      STATUS    ROLES     AGE       VERSION
vagrant   Ready     <none>    15m       v1.8.0

$ kubectl get pods
No resources found.

Podを作ります。

$ kubectl run kuard --image=kuard-run:1 
deployment "kuard" created

$ kubectl get pods
NAME                     READY     STATUS    RESTARTS   AGE
kuard-86d4765c69-4crbk   1/1       Running   0          7m

とりあえず一旦削除します。

kubectl delete deployments/kuard

kuard.yml を用意します。

apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  containers:
  - name: kuard
    image: kuard-run:1
    ports:
      - containerPort: 8080
        name: http
        protocol: TCP

Podが作成されているのを確認します。

$ kubectl get pods
NAME      READY     STATUS    RESTARTS   AGE
kuard     1/1       Running   0          1m

$ kubectl describe  pods kuard
Name:         kuard
Namespace:    default
Node:         vagrant/10.0.2.15
Start Time:   Sat, 22 Feb 2020 13:19:44 +0000
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"kuard","namespace":"default"},"spec":{"containers":[{"image":"kuard-run:1","name":...
Status:       Running
IP:           172.17.0.4
Containers:
  kuard:
    Container ID:   docker://7b17419be3b56fb76e3539a0702782b9a7749252b214d859143064a7eb2aa1ab
    Image:          kuard-run:1
    Image ID:       docker://sha256:d4081f1dfbe6f426cd1ac49dbb16b2f073e5d18af7022e03fa2c674b77b7f7f4
    Port:           8080/TCP
    State:          Running
      Started:      Sat, 22 Feb 2020 13:19:45 +0000
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-hkpw5 (ro)
Conditions:
  Type           Status
  Initialized    True 
  Ready          True 
  PodScheduled   True 
Volumes:
  default-token-hkpw5:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-hkpw5
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     <none>
Events:
  Type    Reason                 Age   From               Message
  ----    ------                 ----  ----               -------
  Normal  Scheduled              1m    default-scheduler  Successfully assigned kuard to vagrant
  Normal  SuccessfulMountVolume  1m    kubelet, vagrant   MountVolume.SetUp succeeded for volume "default-token-hkpw5"
  Normal  Pulled                 1m    kubelet, vagrant   Container image "kuard-run:1" already present on machine
  Normal  Created                1m    kubelet, vagrant   Created container
  Normal  Started                1m    kubelet, vagrant   Started container


$ kubectl logs kuard
2020/02/22 13:19:45 Starting kuard version: v0.10.0-blue

Podを削除します。

$ kubectl delete pods/kuard
pod "kuard" deleted

以上です。

Selenium RemoteWebDriver を試してみる

クライアントPCの上で仮想環境を構築し、その上でSeleniumを動かします。 まずはVagrant仮想マシンを作成します。

# -*- mode: ruby -*-

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-19.10"

  config.vm.network "private_network", ip: "192.168.33.10"

  config.vm.provision "shell", inline: <<-SHELL
    apt-get update -y
    apt update -y
    apt install fonts-ipafont fonts-ipaexfont default-jdk unzip -y

    # Install Google Chrome
    wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
    echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list

    apt update -y
    apt install google-chrome-stable -y
    
    # Install Selenium Environment
    mkdir /home/vagrant/selenium
    cd /home/vagrant/selenium
    wget https://chromedriver.storage.googleapis.com/80.0.3987.16/chromedriver_linux64.zip
    unzip chromedriver_linux64.zip
    wget https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar
  SHELL
end
vagrant up

仮想環境内でHubとNodeの2つのプロセスを立ち上げます。HubとNodeは1:Nの関係になるようです。

Hub

$ cd selenium/
$ DISPLAY=:99.0 java -Dwebdriver.gecko.driver=geckodriver -jar selenium-server-standalone-3.141.59.jar  -role hub

Node

$ DISPLAY=:99.0 java -Dwebdriver.gecko.driver=geckodriver -jar selenium-server-standalone-3.141.59.jar -role node -hub http://localhost:4444

http://192.168.33.10:4444/grid/console をブラウザで開くと現在の状況が確認できます。

Seleniumのコードを用意します。

import org.junit.After
import org.junit.Before
import org.junit.Test
import org.openqa.selenium.OutputType
import org.openqa.selenium.TakesScreenshot
import org.openqa.selenium.WebDriver
import org.openqa.selenium.chrome.ChromeOptions
import org.openqa.selenium.remote.RemoteWebDriver
import java.io.File
import java.net.URL

class RemoteTest {
  var driver: WebDriver? = null
  @Before
  fun setup() {
//    val option = FirefoxOptions() // 今は使えない
    val option = ChromeOptions()
    option.setHeadless(true);
    driver = RemoteWebDriver(URL("http://192.168.33.10:4444/wd/hub"), option)
  }
  @Test
  fun test1() {
    driver!!.get("https://www.yahoo.co.jp/");

    val ss = (driver as TakesScreenshot).getScreenshotAs(OutputType.FILE)
    ss.renameTo(File("./yahoo_top.png"))
  }
  @After
  fun dispose() {
    if (driver != null) {
      driver!!.quit();
    }
  }
}

実行するとホスト側のルートディレクトリにスクリーンショットが保存されます。

f:id:taku-woohar:20200208154440p:plain
実行結果

問題なく実行できました。

過去JJUG CCCの発表資料一覧

個人的にまとまってくれてた方が見返しやすいので作成

Intel DL boostを使おうとした続き

先日の記事ですが、問題解決できました。

#include <immintrin.h>
#include <stdio.h>

int main()
{
    int8_t __attribute__((aligned(64)))   op1_int8[64];
    int8_t __attribute__((aligned(64)))    op2_int8[64];
    int    __attribute__((aligned(64)))   op3_int[16];
    int    __attribute__((aligned(64)))   presult[16];

    int16_t __attribute__((aligned(64)))   op4_int16[32];
    int16_t __attribute__((aligned(64)))   op5_int16[32];

    __m512i  v1_int8;
    __m512i  v2_int8;
    __m512i  v3_int;
    __m512i  v4_int16;
    __m512i  v5_int16;

    for (int i = 0;i < 64;i++) {
        op1_int8[i] = i;
        op2_int8[i] = i;
    }
    for (int i = 0;i < 16;i++) {
        op3_int[i] = 0;
    }
    for (int i = 0;i < 32;i++) {
        op4_int16[i] = i;
        op5_int16[i] = i;
    }

    v1_int8 = _mm512_load_si512(&op1_int8);
    v2_int8 =_mm512_load_si512(&op2_int8);
    v3_int = _mm512_load_si512(&op3_int);
    v4_int16 = _mm512_load_si512(&op4_int16);
    v5_int16 = _mm512_load_si512(&op5_int16);


    printf("vpdpbusds\n");

    __m512i result = _mm512_dpbusds_epi32(v3_int, v1_int8, v2_int8);
    _mm512_store_si512(presult, result);

    for (int i = 0; i < 16; i++) {
        int val = presult[i];
        printf("%d = %d\n", i, val);
    }

    printf("vpmaddwd + vpaddd\n");
    result = _mm512_madd_epi16(v4_int16, v5_int16);
    result = _mm512_add_epi32(result, v3_int);
    _mm512_store_si512(presult, result);
    for (int i = 0; i < 16; i++) {
        int val = presult[i];
        printf("%d = %d\n", i, val);
    }


    return 0;
}

実行結果

vpdpbusds
0 = 14
1 = 126
2 = 366
3 = 734
4 = 1230
5 = 1854
6 = 2606
7 = 3486
8 = 4494
9 = 5630
10 = 6894
11 = 8286
12 = 9806
13 = 11454
14 = 13230
15 = 15134
vpmaddwd + vpaddd
0 = 1
1 = 13
2 = 41
3 = 85
4 = 145
5 = 221
6 = 313
7 = 421
8 = 545
9 = 685
10 = 841
11 = 1013
12 = 1201
13 = 1405
14 = 1625
15 = 1861

なんか vpmaddwd + vpaddd の使い方間違っている気がしますが、気にしない方針で

Intel DL boostを使おうとしたけど

EC2 c5.12xlarge インスタンスでは使えるはずと思って試しました。 Intelのサイトを参考に

sudo apt install gcc-8 clang-8
#include <immintrin.h>
#include <stdio.h>

int main()
{
    int8_t __attribute__((aligned(64)))   op1_int8[64];
    int8_t __attribute__((aligned(64)))    op2_int8[64];
    int    __attribute__((aligned(64)))   op3_int[16];
    int16_t __attribute__((aligned(64)))   op4_int16[32];

    __m512i  v1_int8;
    __m512i  v2_int8;
    __m512i  v3_int;
    __m512i  v4_int16;

    printf("size of int8_t is %zu\n", sizeof(int8_t));
    printf("size of int is %zu\n", sizeof(int));
    printf("size of int16_t is %zu\n", sizeof(int16_t));

    for (int i = 0;i < 64;i++) {
        op1_int8[i] = i;
        op2_int8[i] = i;
    }
    for (int i = 0;i < 16;i++) {
        v3_int[i] = 0;
    }
    for (int i = 0;i < 32;i++) {
        v4_int16[i] = 0;
    }

    v1_int8 =_mm512_load_si512(&op1_int8);
    v2_int8 =_mm512_load_si512(&op2_int8);
    v3_int =_mm512_load_si512(&op3_int);
    v4_int16 =_mm512_load_si512(&op4_int16);

    __m512i result = _mm512_dpbusds_epi32(v3_int, v1_int8, v2_int8);
    int* presult = (int*) &result;

    for (int i = 0; i < 16; i++) {
        printf("%d = %d\n", i, presult[i]);
    }

    return 0;
}

gccコンパイル

gcc-8 -mavx512f -march=icelake-server  main.c -o main

実行結果が以下の通り

size of int8_t is 1
size of int is 4
size of int16_t is 2
0 = 15
1 = 126
2 = 1135245462
3 = 33463
4 = 1135244750
5 = 34583
6 = 2607
7 = 3486
8 = 1135208014
9 = 38359
10 = 1133029965
11 = 41015
12 = 1135254878
13 = 44183
14 = 13230
15 = 15134

明らかに計算結果が違っています。 原因調査中・・・

AVX-512 Vector Neural Network Instructions (VNNI) - x86 - WikiChip