यह अच्छा प्रश्न है। ऐसे कई कारण हैं जिन्हें आप वास्तव में केवल स्वैप निर्देशांक के बजाय स्मृति में मैट्रिक्स को स्थानांतरित करना चाहते हैं, उदाहरण के लिए मैट्रिक्स गुणन और गौसियन स्मीयरिंग में।
पहले मुझे ट्रांसफ़र के लिए उपयोग किए जाने वाले कार्यों में से एक को सूचीबद्ध करने दें ( EDIT: कृपया मेरे उत्तर का अंत देखें जहाँ मुझे बहुत तेज़ गति मिली )
void transpose(float *src, float *dst, const int N, const int M) {
#pragma omp parallel for
for(int n = 0; n<N*M; n++) {
int i = n/N;
int j = n%N;
dst[n] = src[M*j + i];
}
}
अब देखते हैं कि संक्रमण क्यों उपयोगी है। मैट्रिक्स गुणा C = A * B पर विचार करें। हम इसे इस तरह से कर सकते थे।
for(int i=0; i<N; i++) {
for(int j=0; j<K; j++) {
float tmp = 0;
for(int l=0; l<M; l++) {
tmp += A[M*i+l]*B[K*l+j];
}
C[K*i + j] = tmp;
}
}
इस तरह, हालांकि, कैश की बहुत कमी होने वाली है। एक बहुत तेजी से समाधान पहले बी का संक्रमण लेना है
transpose(B);
for(int i=0; i<N; i++) {
for(int j=0; j<K; j++) {
float tmp = 0;
for(int l=0; l<M; l++) {
tmp += A[M*i+l]*B[K*j+l];
}
C[K*i + j] = tmp;
}
}
transpose(B);
मैट्रिक्स गुणन O (n ^ 3) है और पारगमन O (n ^ 2) है, इसलिए पारगमन लेने का अभिकलन समय (बड़े के लिए n
) पर एक नगण्य प्रभाव होना चाहिए । मैट्रिक्स गुणन में लूप टाइलिंग ट्रांसपोज़ लेने की तुलना में और भी अधिक प्रभावी है, लेकिन यह बहुत अधिक जटिल है।
काश, मैं ट्रांसपोज़ करने का एक तेज़ तरीका जानता ( संपादित करें: मुझे एक तेज़ समाधान मिला, मेरे उत्तर का अंत देखें )। जब कुछ हफ्तों में हसवेल / एवीएक्स 2 बाहर निकलता है, तो यह एक इकट्ठा समारोह होगा। मुझे नहीं पता कि यह इस मामले में मददगार होगा या नहीं, लेकिन मैं एक कॉलम इकट्ठा करने और एक पंक्ति लिखने की छवि बना सकता हूं। शायद यह परिवर्तन को अनावश्यक बना देगा।
गौसियन स्मीयरिंग के लिए आप जो करते हैं वह क्षैतिज रूप से स्मीयर होता है और फिर लंबवत स्मीयर करता है। लेकिन लंबवत स्मीयर करने से कैश की समस्या होती है तो आप क्या करते हैं
Smear image horizontally
transpose output
Smear output horizontally
transpose output
यहाँ Intel द्वारा एक पेपर दिया गया है जिसमें बताया गया है कि
http://software.intel.com/en-us/articles/iir-gaussian-blur-filter-implementation-using-intel-advanced-vector-extensions
अंत में, मैं वास्तव में मैट्रिक्स गुणा (और गौसियन स्मीयरिंग) में क्या करता हूं, बिल्कुल ट्रांसपोज़ नहीं है, लेकिन एक निश्चित वेक्टर आकार (जैसे एसएसई / एवीएक्स के लिए 4 या 8) की चौड़ाई में ट्रांज़ोज़ लें। यहां वह फ़ंक्शन है जो मैं उपयोग करता हूं
void reorder_matrix(const float* A, float* B, const int N, const int M, const int vec_size) {
#pragma omp parallel for
for(int n=0; n<M*N; n++) {
int k = vec_size*(n/N/vec_size);
int i = (n/vec_size)%N;
int j = n%vec_size;
B[n] = A[M*i + k + j];
}
}
संपादित करें:
मैंने बड़े मैट्रिसेस के लिए सबसे तेज़ बदलाव खोजने के लिए कई फंक्शन की कोशिश की। अंत में सबसे तेज़ परिणाम लूप ब्लॉकिंग का उपयोग करना है block_size=16
( संपादित करें: मुझे SSE और लूप ब्लॉकिंग का उपयोग करके एक तेज़ समाधान मिला - नीचे देखें )। यह कोड किसी भी NxM मैट्रिक्स के लिए काम करता है (यानी मैट्रिक्स का वर्ग नहीं होना चाहिए)।
inline void transpose_scalar_block(float *A, float *B, const int lda, const int ldb, const int block_size) {
#pragma omp parallel for
for(int i=0; i<block_size; i++) {
for(int j=0; j<block_size; j++) {
B[j*ldb + i] = A[i*lda +j];
}
}
}
inline void transpose_block(float *A, float *B, const int n, const int m, const int lda, const int ldb, const int block_size) {
#pragma omp parallel for
for(int i=0; i<n; i+=block_size) {
for(int j=0; j<m; j+=block_size) {
transpose_scalar_block(&A[i*lda +j], &B[j*ldb + i], lda, ldb, block_size);
}
}
}
मान lda
और ldb
मैट्रिक्स की चौड़ाई हैं। इनको ब्लॉक साइज का गुणक होना चाहिए। मूल्यों को खोजने के लिए और जैसे कि एक 3000x1001 मैट्रिक्स मैं इस तरह से कुछ के लिए मेमोरी आवंटित करने के लिए
#define ROUND_UP(x, s) (((x)+((s)-1)) & -(s))
const int n = 3000;
const int m = 1001;
int lda = ROUND_UP(m, 16);
int ldb = ROUND_UP(n, 16);
float *A = (float*)_mm_malloc(sizeof(float)*lda*ldb, 64);
float *B = (float*)_mm_malloc(sizeof(float)*lda*ldb, 64);
3000x1001 के लिए यह रिटर्न ldb = 3008
और lda = 1008
संपादित करें:
मैंने SSE इंट्रिनिक्स का उपयोग करके एक और भी तेज समाधान पाया:
inline void transpose4x4_SSE(float *A, float *B, const int lda, const int ldb) {
__m128 row1 = _mm_load_ps(&A[0*lda]);
__m128 row2 = _mm_load_ps(&A[1*lda]);
__m128 row3 = _mm_load_ps(&A[2*lda]);
__m128 row4 = _mm_load_ps(&A[3*lda]);
_MM_TRANSPOSE4_PS(row1, row2, row3, row4);
_mm_store_ps(&B[0*ldb], row1);
_mm_store_ps(&B[1*ldb], row2);
_mm_store_ps(&B[2*ldb], row3);
_mm_store_ps(&B[3*ldb], row4);
}
inline void transpose_block_SSE4x4(float *A, float *B, const int n, const int m, const int lda, const int ldb ,const int block_size) {
#pragma omp parallel for
for(int i=0; i<n; i+=block_size) {
for(int j=0; j<m; j+=block_size) {
int max_i2 = i+block_size < n ? i + block_size : n;
int max_j2 = j+block_size < m ? j + block_size : m;
for(int i2=i; i2<max_i2; i2+=4) {
for(int j2=j; j2<max_j2; j2+=4) {
transpose4x4_SSE(&A[i2*lda +j2], &B[j2*ldb + i2], lda, ldb);
}
}
}
}
}