インテル® Xeon Phi™ プロセッサー/コプロセッサーの実行性能の比較
インテル® Xeon Phi™ プロセッサーと既存のインテル® Xeon® プロセッサーおよびインテル® Xeon Phi™ コロセッサーとの実行性能の比較
概要
この記事は、インテル® Xeon Phi™ コプロセッサー (Knights Corner – KNC) とインテル® Xeon® プロセッサーを搭載したシステムとインテル® Xeon Phi™ プロセッサー (Knights Landing – KNL) を搭載したシステムを使用して KNL の性能を比較した際の検証内容を紹介しています。
いままでのインテル® Xeon Phi™ コプロセッサーでは、PCI-Express 転送によるボトルネックや、搭載メモリー (最大 16GB) などのハードウェアの制約により、大規模な配列を効果的に処理させるためには工夫が必要とされてきました。
KNL は KNC と異なり、汎用 OS をブート可能なホストプロセッサーとして動作します。これにより、インテル® Xeon® プロセッサーやインテル® Core™ プロセッサー上で動作しているアプリケーションをインテル® MIC アーキテクチャー向けに再コンパイルする必要なく、インテル® Xeon Phi™ プロセッサー上で動作させることができます。
ここでは、既存のインテル® Xeon® プロセッサー上で動作する最適化されたプログラムをインテル® Xeon Phi™ プロセッサー上で動作させた場合の性能をテストした結果をまとめました。
- 検証に用いたインテル® Distribution for Python* はベータ版です。
製品版とは結果が異なる可能性があります。 - 本記事の内容は iSUS 編集部で検証を行ったものですが、性能を保証するものではありません。
ご了承ください。
実行環境
実行環境 | |||
---|---|---|---|
システム 1 | システム 1-1 | システム 2 | |
プロセッサー詳細 | インテル® Xeon® プロセッサー E5 2740 | インテル® Xeon Phi™ コプロセッサー 5110P | インテル® Xeon Phi™ プロセッサー 7210 |
プロセッサー数 | 2 | 1 | 1 |
物理コア数/論理コア数 | 12/24 (HT 無効) | 60/240 | 64/256 |
搭載メモリ | DDR3 96GB | GDDR5 8GB |
DDR4 96GB MCDRAM 16GB |
OS | RHEL 6.4 | mpss 3.4 | CentOS 7.2 |
比較内容
ここでは下記の処理を実行して、性能比較を行いました。
1. DGEMM (インテル® MKL)
#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include "mkl.h" void do_work(double C[], double A[], double B[], int N) { int i, j, k; double alpha = 1.0, beta = 1.0; #pragma omp parallel for for( i=0; i<N; i++ ){ for( j=0; j<N; j++ ){ C[i*N + j] = 0.0; A[i*N + j] = j&(1<<10) - 512; B[i*N + j] = (j%2) ? 1.0 : -1.0; } } cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, N, N, N, alpha, A, N, B, N, beta, C, N); } int main(void) { long i; int N = 6000; //Matrix Size double *A, *B, *C; struct timeval start, end; long mtime, seconds, useconds; A = (double *)mkl_malloc(sizeof(double)*N*N, 64); B = (double *)mkl_malloc(sizeof(double)*N*N, 64); C = (double *)mkl_malloc(sizeof(double)*N*N, 64); printf("\nMatrix size: [%d,%d]\n\n",N,N); for( i=0; i<6; i++) { gettimeofday(&start, NULL); do_work(C, A, B, N); gettimeofday(&end, NULL); seconds = end.tv_sec - start.tv_sec; useconds = end.tv_usec - start.tv_usec; mtime = ((seconds) * 1000 + useconds/1000.0) + 0.5; printf("Time taken to run Matmul: %ld milliseconds (%lf)\n", mtime, C[N*N-1]); fflush(stdout); } mkl_free(A); mkl_free(B); mkl_free(C); return 0; }
図 1: DGEMM を使用したサンプルコード
2. numpy.dot (インテル® Distribution for Python*)
import numpy as np import time N = 6000 print ('Initializing...') A = np.matrix(np.random.random((N, N)), dtype=np.double) B = np.matrix(np.random.random((N, N)), dtype=np.double) print ('\nMatrix size: [{0}, {1}]\n'.format(N, N)) for i in range(1,6): start = time.time() C = np.dot(A, B) end = time.time() tm = int((end - start) * 1000) print ('Run numpy.dot {0} milliseconds'.format(tm))
図 3: Numpy.dot を使用したサンプルコード
図 3 は numpy ライブラリーに含まれている dot ルーチンを DGEMM と同様に複数回実行するスクリプトです。図 4 は各システム上での実行性能を比較しています。Python* は インテル® Distribution for Python* を使用してインテル® プロセッサー向けに高速に動作するように最適化しています。(インテル® Distribution for Python の詳細はこちらをご確認ください。)
3. DGEMV (インテル® MKL)
#include<stdio.h> #include<stdlib.h> #include <sys/time.h> #include <malloc.h> #include <hbwmalloc.h> #include "mkl.h" #define SIZE 15000 main() { long i, j; double *A; double *x, *y; long m = SIZE, n = SIZE; int ONE = 1; double alpha = 1, beta = 1; struct timeval start, end; long mtime, seconds, useconds; int retA = posix_memalign((void*) &A, 64, sizeof(double)*(n * m)); int retB = posix_memalign((void*) &x, 64, sizeof(double)*n); int retC = posix_memalign((void*) &y, 64, sizeof(double)*n); printf("\nMatrix size\n"); printf("A : [%d,%d]\n",n,m); printf("x : [%d]\n",n); printf("y : [%d]\n",n); printf("\n"); gettimeofday(&start, NULL); #pragma omp parallel for for (i = 0; i < m; i++){ x[i] = 1.0; y[i] = 1.0; for (j = 0; j < n; j++) { A[i*n + j] = j%4 ; } } cblas_dgemv(CblasRowMajor,CblasNoTrans, m, n, alpha, A, m, x, ONE, beta, y, ONE); gettimeofday(&end, NULL); seconds = end.tv_sec - start.tv_sec; useconds = end.tv_usec - start.tv_usec; mtime = ((seconds) * 1000 + useconds / 1000.0) + 0.5; printf("Time taken to run dgemv: %ld milliseconds\n", mtime); for (j = 0; j<5; j++) { printf("%f ", y[j]); } printf("\n"); free(A); free(x); free(y); printf("\n"); }
図 5: DGEMV のサンプルコード
まとめ
今回は、インテル® Xeon® プロセッサーで動作する既存のプログラムをコードレベルの変更を行わず、インテル® Xeon Phi™ プロセッサーでそのまま動作させて実行性能の比較を行いました。またインテル® Xeon Phi™ コプロセッサー上ではインテル® MIC アーキテクチャー向けのバイナリーを生成して比較しました。これらは既に並列化処理を実装しているため、インテル® Xeon Phi™ プロセッサーではより実行性能が向上していることが確認できます。
検証では少ししか触れていませんが、インテル® Xeon Phi™ プロセッサーには DDR4 メモリに加え、独自に 16 GB の MCDRAMを搭載しているため、頻繁に使用されるデータを MCDRAM に配置することで、より性能が向上する可能性があります。インテル® MKL の関数は可能であれば MCDRAM に配列を確保するため、より顕著な性能の向上が見られました。また、ベクトル命令として AVX-512 を使用することもできます。より高度なベクトル命令を使用することで、より高速に動作する可能性があります。
なお、KNLでは、KNC 同様に並列化されていない処理は、一般的なインテル® Xeon® プロセッサーよりも遅くなってしまいます。複数の並列レベルを意識したプログラムを開発することで、インテル® Xeon Phi™ プロセッサーのリソースを効果的に利用することができます。
ソフトウェアの最適化については、現代化の記事もご参照ください。