সবার আগে, এই প্রশ্ন / চ্যালেঞ্জ পোস্ট করার জন্য ধন্যবাদ! অস্বীকৃতি হিসাবে, আমি কিছু ফোর্টরান অভিজ্ঞতা সহ একটি নেটিভ সি প্রোগ্রামার, এবং সিতে বাড়ির বেশিরভাগ বোধ করি, সুতরাং আমি কেবল সি সংস্করণের উন্নতিতে মনোনিবেশ করব। আমি সমস্ত ফোর্টরান হ্যাকগুলিকে তাদের যাওয়ার জন্য আমন্ত্রণ জানাই!
কেবল নতুনদের এটি সম্পর্কে কী তা মনে করিয়ে দেওয়ার জন্য: এই থ্রেডের প্রাথমিক ভিত্তিটি ছিল যে জিসিসি / ফোর্ট্রান এবং আইসিসি / আইফোর্টের যথাক্রমে একই ব্যাক-এন্ডস থাকা উচিত, নির্বিশেষে একই (শব্দার্থবিজ্ঞানযুক্ত) প্রোগ্রামের জন্য সমমানের কোড তৈরি করা উচিত এটি সি বা ফোর্টরানে রয়েছে। ফলাফলের মানটি কেবলমাত্র সংশ্লিষ্ট বাস্তবায়নের মানের উপর নির্ভর করে।
আমি কিছুটা কোড দিয়ে এবং আমার কম্পিউটারে (থিংকপ্যাড 201x, ইন্টেল কোর আই 5 এম 560, 2.67 গিগাহার্টজ) gcc
4.6.1 এবং নিম্নলিখিত সংকলক পতাকা ব্যবহার করে খেললাম :
GCCFLAGS= -O3 -g -Wall -msse2 -march=native -funroll-loops -ffast-math -fomit-frame-pointer -fstrict-aliasing
আমি আরও এগিয়ে গিয়েছিলাম এবং সি ++ কোডের একটি সিমডি-ভেক্টরাইজড সি-ভাষা সংস্করণ লিখেছিলাম spectral_norm_vec.c
:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
/* Define the generic vector type macro. */
#define vector(elcount, type) __attribute__((vector_size((elcount)*sizeof(type)))) type
double Ac(int i, int j)
{
return 1.0 / ((i+j) * (i+j+1)/2 + i+1);
}
double dot_product2(int n, double u[], double v[])
{
double w;
int i;
union {
vector(2,double) v;
double d[2];
} *vu = u, *vv = v, acc[2];
/* Init some stuff. */
acc[0].d[0] = 0.0; acc[0].d[1] = 0.0;
acc[1].d[0] = 0.0; acc[1].d[1] = 0.0;
/* Take in chunks of two by two doubles. */
for ( i = 0 ; i < (n/2 & ~1) ; i += 2 ) {
acc[0].v += vu[i].v * vv[i].v;
acc[1].v += vu[i+1].v * vv[i+1].v;
}
w = acc[0].d[0] + acc[0].d[1] + acc[1].d[0] + acc[1].d[1];
/* Catch leftovers (if any) */
for ( i = n & ~3 ; i < n ; i++ )
w += u[i] * v[i];
return w;
}
void matmul2(int n, double v[], double A[], double u[])
{
int i, j;
union {
vector(2,double) v;
double d[2];
} *vu = u, *vA, vi;
bzero( u , sizeof(double) * n );
for (i = 0; i < n; i++) {
vi.d[0] = v[i];
vi.d[1] = v[i];
vA = &A[i*n];
for ( j = 0 ; j < (n/2 & ~1) ; j += 2 ) {
vu[j].v += vA[j].v * vi.v;
vu[j+1].v += vA[j+1].v * vi.v;
}
for ( j = n & ~3 ; j < n ; j++ )
u[j] += A[i*n+j] * v[i];
}
}
void matmul3(int n, double A[], double v[], double u[])
{
int i;
for (i = 0; i < n; i++)
u[i] = dot_product2( n , &A[i*n] , v );
}
void AvA(int n, double A[], double v[], double u[])
{
double tmp[n] __attribute__ ((aligned (16)));
matmul3(n, A, v, tmp);
matmul2(n, tmp, A, u);
}
double spectral_game(int n)
{
double *A;
double u[n] __attribute__ ((aligned (16)));
double v[n] __attribute__ ((aligned (16)));
int i, j;
/* Aligned allocation. */
/* A = (double *)malloc(n*n*sizeof(double)); */
if ( posix_memalign( (void **)&A , 4*sizeof(double) , sizeof(double) * n * n ) != 0 ) {
printf( "spectral_game:%i: call to posix_memalign failed.\n" , __LINE__ );
abort();
}
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
A[i*n+j] = Ac(i, j);
}
}
for (i = 0; i < n; i++) {
u[i] = 1.0;
}
for (i = 0; i < 10; i++) {
AvA(n, A, u, v);
AvA(n, A, v, u);
}
free(A);
return sqrt(dot_product2(n, u, v) / dot_product2(n, v, v));
}
int main(int argc, char *argv[]) {
int i, N = ((argc >= 2) ? atoi(argv[1]) : 2000);
for ( i = 0 ; i < 10 ; i++ )
printf("%.9f\n", spectral_game(N));
return 0;
}
তিনটি সংস্করণ একই পতাকা এবং একই gcc
সংস্করণ দিয়ে সংকলিত হয়েছিল । মনে রাখবেন যে আরও সঠিক সময় পেতে আমি 0..9 থেকে একটি লুপে মূল ফাংশন কলটি গুটিয়ে রেখেছি।
$ time ./spectral_norm6 5500
1.274224153
...
real 0m22.682s
user 0m21.113s
sys 0m1.500s
$ time ./spectral_norm7 5500
1.274224153
...
real 0m21.596s
user 0m20.373s
sys 0m1.132s
$ time ./spectral_norm_vec 5500
1.274224153
...
real 0m21.336s
user 0m19.821s
sys 0m1.444s
সুতরাং "আরও ভাল" সংকলক পতাকা সহ, সি ++ সংস্করণটি ফোর্টরান সংস্করণটি সম্পাদন করে এবং হ্যান্ড-কোডেড ভেক্টরাইজ লুপগুলি কেবল একটি প্রান্তিক উন্নতি সরবরাহ করে। সি ++ সংস্করণটির জন্য এসেম্বলারের একটি তাত্ক্ষণিক দৃষ্টিভঙ্গি দেখায় যে মূল লুপগুলিও ভেক্টরাইজ করা হয়েছে, যদিও আরও আক্রমণাত্মকভাবে অনিয়ন্ত্রিত হয়ে পড়েছে।
আমার দ্বারা উত্পাদিত এসেম্বলারের দিকেও আমার নজর ছিল gfortran
এবং এটি এখানে বিস্ময়কর: কোনও ভেক্টরাইজেশন নেই। আমি এই সত্যকে দায়ী করি যে এটি ব্যান্ডউইথের সীমাবদ্ধ হওয়ার কারণে সমস্যাটি কেবলমাত্র আর্কিটেকচারের তুলনায় সামান্য ধীর। প্রতিটি ম্যাট্রিক্সের গুণকগুলির জন্য, 230MB ডেটা ট্র্যাভারস করা হয়েছে, যা খুব বেশি পরিমাণে ক্যাশের সমস্ত স্তরকে সরিয়ে দেয়। আপনি যদি একটি ছোট ইনপুট মান ব্যবহার করেন, উদাহরণস্বরূপ 100
, পারফরম্যান্সের পার্থক্যগুলি যথেষ্ট বেড়ে যায়।
পার্শ্ব-নোট হিসাবে, ভেক্টরাইজেশন, প্রান্তিককরণ এবং সংকলক পতাকাগুলি সম্পর্কে অবলম্বন না করে, সুনির্দিষ্ট সুস্পষ্ট অপ্টিমাইজেশনটি হ'ল একক-নির্ভুল গাণিতিকের প্রথম কয়েকটি পুনরাবৃত্তি গণনা করা, যতক্ষণ না আমরা ফলাফলের ~ 8 সংখ্যা পাই। একক-নির্ভুল নির্দেশাবলী কেবল দ্রুত নয়, তবে যে পরিমাণ মেমরির আশপাশে স্থানান্তর করতে হবে তাও অর্ধেক হয়ে যায়।