C ++ में मैट्रिक्स को स्थानांतरित करने का सबसे तेज़ तरीका क्या है?


81

मेरे पास एक मैट्रिक्स (अपेक्षाकृत बड़ा) है जिसे मुझे स्थानांतरित करने की आवश्यकता है। उदाहरण के लिए मान लें कि मेरा मैट्रिक्स है

a b c d e f
g h i j k l
m n o p q r 

मैं चाहता हूं कि परिणाम इस प्रकार हो:

a g m
b h n
c I o
d j p
e k q
f l r

ऐसा करने का सबसे तेज़ तरीका क्या है?


2
इसे "ट्रांसपोज़िंग" कहा जाता है। 90 डिग्री तक घूमना एक पूरी तरह से अलग धारणा है।
एंडी प्रोल

35
और सबसे तेज़ तरीका इसे घुमाने के लिए नहीं है, लेकिन जब आप सरणी तक पहुंचते हैं तो बस इंडेक्स ऑर्डर को स्वैप करना है।
उच्च प्रदर्शन मार्क

2
कोई फर्क नहीं पड़ता कि यह कितना तेज़ है, आपको मैट्रिक्स के सभी तत्वों को वैसे भी एक्सेस करना होगा।
ताना

10
@HighPerformanceMark: मुझे लगता है कि यह निर्भर करता है, यदि आप तब पंक्ति क्रम में मैट्रिक्स को दोहराव से एक्सेस करना चाहते हैं, तो "ट्रांसपोज़्ड" झंडा होने से आपको बहुत कठिनाई होगी।
मथिउ एम।

3
मेमोरी कैश के साथ होने वाली समस्याओं के लिए ट्रांसपेरिंग मैट्रीज़ कुख्यात है। यदि आपकी सरणी इतनी बड़ी है कि एक स्थानान्तरण का प्रदर्शन महत्वपूर्ण है, और आप केवल स्वैप किए गए सूचकांकों के साथ एक इंटरफ़ेस प्रदान करके ट्रांसपोज़िंग से बच नहीं सकते हैं, तो आपका सबसे अच्छा विकल्प बड़े मैट्रिसेस को ट्रांसपोज़ करने के लिए मौजूदा लाइब्रेरी रूटीन का उपयोग करना है। विशेषज्ञों ने पहले से ही यह काम किया है, और आपको इसका उपयोग करना चाहिए।
एरिक पोस्टपिसिल

जवाबों:


131

यह अच्छा प्रश्न है। ऐसे कई कारण हैं जिन्हें आप वास्तव में केवल स्वैप निर्देशांक के बजाय स्मृति में मैट्रिक्स को स्थानांतरित करना चाहते हैं, उदाहरण के लिए मैट्रिक्स गुणन और गौसियन स्मीयरिंग में।

पहले मुझे ट्रांसफ़र के लिए उपयोग किए जाने वाले कार्यों में से एक को सूचीबद्ध करने दें ( 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);
                }
            }
        }
    }
}

1
अच्छा शॉट है, लेकिन मुझे यकीन नहीं है कि 'मैट्रिक्स गुणन हे (n ^ 3)' है, मुझे लगता है कि यह O (n) 2 है।
ulyssis2

2
@ ulyssis2 इट्स O (n ^ 3), जब तक कि आप स्ट्रैसन मैट्रिक्स मैट्रिक्स का उपयोग नहीं करते (O (n ^ 2.8074))। user2088790: यह बहुत अच्छी तरह से किया जाता है। इसे अपने व्यक्तिगत संग्रह में रखते हुए। :)
saurabheights

10
मामले में, कोई भी जानना चाहता है कि यह उत्तर किसने लिखा था। मैं एक बार एसओ को छोड़ दिया था, उस पर चढ़ गया, और वापस आ गया।
Z बोसोन

1
@ ulyssis2 Naive मैट्रिक्स गुणन निश्चित रूप से O (n ^ 3) है, और, जहां तक ​​मुझे पता है, गणना कर्नेल भोले एल्गोरिथ्म को लागू करते हैं (मुझे लगता है कि यह इसलिए है क्योंकि स्ट्रैसन का अंत अधिक संचालन (परिवर्धन) कर रहा है, जो बुरा है अगर आप तेजी से उत्पाद कर सकते हैं, लेकिन मैं गलत हो सकता है)। यह एक खुली समस्या है कि मैट्रिक्स गुणा O (n ^ 2) हो सकता है या नहीं।
इटाले-कॉहोमोलॉजी

आमतौर पर आपके लिए काम करने के लिए एक रैखिक बीजगणित पुस्तकालय पर भरोसा करना बेहतर विकल्प होता है। इंटेल एमकेएल, ओपनबीएलएएस आदि जैसे आधुनिक दिन पुस्तकालय गतिशील सीपीयू प्रेषण प्रदान करते हैं जो आपके हार्डवेयर के लिए उपलब्ध सर्वोत्तम कार्यान्वयन का चयन करता है (उदाहरण के लिए, एसएसई की तुलना में व्यापक वेक्टर रजिस्टर उपलब्ध हो सकता है: एवीएक्स एवीएक्स 2, एवीएक्स 512 ..., इसलिए आप डॉन फास्ट प्रोग्राम प्राप्त करने के लिए गैर-पोर्टेबल प्रोग्राम बनाने की आवश्यकता नहीं है।
जॉर्ज बेलन

39

यह आपके एप्लिकेशन पर निर्भर करने वाला है, लेकिन सामान्य तौर पर मैट्रिक्स को स्थानांतरित करने का सबसे तेज़ तरीका आपके निर्देशांक को उल्टा करना होगा जब आप एक नज़र डालते हैं, तो आपको वास्तव में कोई डेटा स्थानांतरित करने की आवश्यकता नहीं होती है।


32
यह बहुत अच्छा है अगर यह एक छोटा मैट्रिक्स है या आप इसे केवल एक बार पढ़ते हैं। हालाँकि, यदि ट्रांसपोज़्ड मैट्रिक्स बड़ा है और कई बार पुन: उपयोग करने की आवश्यकता है, तो आप बेहतर मेमोरी एक्सेस पैटर्न प्राप्त करने के लिए तेज़ ट्रांसपोज़्ड संस्करण को सहेज सकते हैं। (+1, btw)
एजेंटलीयन

2
@Agentlien: A [j] [i] A [i] [j] की तुलना में कोई धीमा क्यों होगा?
बीकर

32
@ बीकर यदि आपके पास एक बड़ी मैट्रिक्स है, तो अलग-अलग पंक्तियों / स्तंभों में अलग-अलग कैश लाइनें / पृष्ठ हो सकते हैं। इस मामले में, आप तत्वों पर इस तरह से चलना चाहते हैं कि आप एक दूसरे के बाद आसन्न तत्वों तक पहुँच प्राप्त करें। अन्यथा, यह कैश मिस बनने वाले हर तत्व तक पहुंच सकता है, जो प्रदर्शन को पूरी तरह से नष्ट कर देता है।
एजेंटलीयन

10
@ बीकर: इसका सीपीयू स्तर पर कैशिंग के साथ करना है (यह देखते हुए कि मैट्रिक्स मेमोरी का एक बड़ा ब्लब है), कैश लाइनें फिर मैट्रिक्स की प्रभावी लाइनें हैं, और प्रीफ़ेचर अगले कुछ लाइनों को ला सकता है। यदि आप एक्सेस को स्विच करते हैं, तो सीपीयू कैश / प्रीफैचर अभी भी लाइन से काम करता है जब आप कॉलम द्वारा कॉलम तक पहुंचते हैं, तो प्रदर्शन ड्रॉप नाटकीय हो सकता है।
मैथ्यू एम।

2
@taocp मूल रूप से, आपको संकेतित करने के लिए किसी प्रकार के झंडे की आवश्यकता होगी और फिर यह कहने के लिए अनुरोध किया (i,j)जाएगा कि मैप किया जाएगा(j,i)
Shafik Yaghmour

5

4x4 वर्ग फ़्लोट को स्थानांतरित करने के बारे में कुछ विवरण (मैं बाद में 32-बिट पूर्णांक पर चर्चा करूंगा) x86 हार्डवेयर के साथ मैट्रिसेस। 8x8 या 16x16 जैसे बड़े वर्ग मैट्रिसेस को स्थानांतरित करने के लिए यहां शुरू करना सहायक है।

_MM_TRANSPOSE4_PS(r0, r1, r2, r3)अलग-अलग कंपाइलरों द्वारा अलग-अलग कार्यान्वित किया जाता है। जीसीसी और आईसीसी (मैंने क्लैंग की जाँच नहीं की है) unpcklps, unpckhps, unpcklpd, unpckhpdजबकि MSVC केवल उपयोग करता है shufps। हम वास्तव में इन दोनों दृष्टिकोणों को एक साथ जोड़ सकते हैं।

t0 = _mm_unpacklo_ps(r0, r1);
t1 = _mm_unpackhi_ps(r0, r1);
t2 = _mm_unpacklo_ps(r2, r3);
t3 = _mm_unpackhi_ps(r2, r3);

r0 = _mm_shuffle_ps(t0,t2, 0x44);
r1 = _mm_shuffle_ps(t0,t2, 0xEE);
r2 = _mm_shuffle_ps(t1,t3, 0x44);
r3 = _mm_shuffle_ps(t1,t3, 0xEE);

एक दिलचस्प अवलोकन यह है कि दो फेरबदल को इस तरह एक फेरबदल और दो मिश्रणों (SSE4.1) में बदला जा सकता है।

t0 = _mm_unpacklo_ps(r0, r1);
t1 = _mm_unpackhi_ps(r0, r1);
t2 = _mm_unpacklo_ps(r2, r3);
t3 = _mm_unpackhi_ps(r2, r3);

v  = _mm_shuffle_ps(t0,t2, 0x4E);
r0 = _mm_blend_ps(t0,v, 0xC);
r1 = _mm_blend_ps(t2,v, 0x3);
v  = _mm_shuffle_ps(t1,t3, 0x4E);
r2 = _mm_blend_ps(t1,v, 0xC);
r3 = _mm_blend_ps(t3,v, 0x3);

यह प्रभावी रूप से 4 फेरबदल को 2 फेरबदल और 4 मिश्रणों में परिवर्तित करता है। यह GCC, ICC और MSVC के कार्यान्वयन की तुलना में 2 अधिक निर्देशों का उपयोग करता है। लाभ यह है कि यह पोर्ट दबाव को कम करता है जिसका कुछ परिस्थितियों में लाभ हो सकता है। वर्तमान में सभी फेरबदल और अनपैक्स केवल एक विशेष पोर्ट पर जा सकते हैं जबकि मिश्रण दो अलग-अलग पोर्टों में जा सकते हैं।

मैंने MSVC जैसे 8 फेरबदल का उपयोग करने की कोशिश की और इसे 4 फेरबदल + 8 मिश्रणों में परिवर्तित किया लेकिन यह काम नहीं किया। मुझे अभी भी 4 अनपैक्स का उपयोग करना था।

मैंने 8x8 फ्लोट ट्रांस्पोज़ (उस उत्तर के अंत की ओर देखें) के लिए इस तकनीक का उपयोग किया। https://stackoverflow.com/a/25627536/2542702 । उस उत्तर में मुझे अभी भी 8 अनपैक का उपयोग करना था लेकिन मैंने 8 फेरबदल को 4 फेरबदल और 8 मिश्रणों में बदलने का प्रयास किया।

32-बिट पूर्णांकों के लिए ऐसा कुछ नहीं है shufps(केवल AVX512 के साथ 128-बिट फेरबदल को छोड़कर), इसलिए इसे केवल अनपैक्स के साथ लागू किया जा सकता है जो मुझे नहीं लगता कि मिश्रणों (कुशलता से) में परिवर्तित हो सकते हैं। AVX512 के साथ 32-बिट फ़्लोट के बजाय 4-पूर्णांक के 128-बिट लेन को छोड़कर vshufi32x4प्रभावी रूप से कार्य करता है, shufpsइसलिए vshufi32x4कुछ मामलों में संभवतः यही तकनीक हो सकती है । शूरवीरों के साथ लैंडिंग फेरबदल मिश्रणों की तुलना में चार गुना धीमे (थ्रूपुट) होते हैं।


1
आप shufpsपूर्णांक डेटा पर उपयोग कर सकते हैं । आप की उथल की एक बहुत कुछ कर रहे हैं, यह इसके लिए एफपी डोमेन यह सब करने के लिए लायक हो सकता है shufps+ blendps, खासकर यदि आप समान रूप से कुशल AVX2 नहीं है vpblenddउपलब्ध। इसके अलावा, इंटेल SnB- परिवार के हार्डवेयर पर, shufpsपूर्णांक निर्देशों के बीच उपयोग करने के लिए कोई अतिरिक्त बाईपास देरी नहीं है paddd। (वहाँ के मिश्रण के लिए एक बाईपास देरी है blendpsके साथ paddd, Agner कोहरा के SNB परीक्षण के अनुसार, हालांकि।)
पीटर Cordes

@PeterCordes, मुझे फिर से डोमेन परिवर्तनों की समीक्षा करने की आवश्यकता है। क्या कोई तालिका है (शायद SO पर एक उत्तर) जो सारांश देता है कि Core2-Skylake के लिए डोमेन परिवर्तन जुर्माना है? किसी भी मामले में मैंने इस पर अधिक विचार दिया है। अब मैं देख रहा हूं कि क्यों आप और vinsertf64x4मेरे 16x16 के बजाय उत्तर देने का उल्लेख करते रहे vinserti64x4। यदि मैं पढ़ रहा हूं तो मैट्रिक्स लिखना तो निश्चित रूप से कोई फर्क नहीं पड़ता है अगर मैं फ़्लोटिंग पॉइंट डोमेन या पूर्णांक डोमेन का उपयोग करता हूं क्योंकि ट्रांज़ोज़ केवल डेटा स्थानांतरित कर रहा है।
जेड बोसोन

1
Agner की तालिकाएँ Core2 और Nehalem (और AMD I थिंक) के लिए प्रति-निर्देश डोमेन को सूचीबद्ध करती हैं, लेकिन SnB- परिवार को नहीं। एग्नर के माइक्रो गाइड में सिर्फ एक पैराग्राफ है, जिसमें कहा गया है कि यह 1c से कम है और अक्सर SnB पर 0, कुछ उदाहरणों के साथ। इंटेल के अनुकूलन मैनुअल में एक मेज है जो मुझे लगता है, लेकिन मैंने इसे ग्रो करने की कोशिश नहीं की है इसलिए मुझे याद नहीं है कि इसका कितना विवरण है। मुझे याद है कि यह पूरी तरह से स्पष्ट नहीं है कि एक दिया गया निर्देश किस श्रेणी में होगा।
पीटर कॉर्ड्स

यहां तक ​​कि अगर आप सिर्फ मेमोरी में वापस नहीं लिख रहे हैं, तो यह पूरे ट्रांसपोज़ के लिए केवल 1 अतिरिक्त घड़ी है। प्रत्येक ऑपरेंड के लिए अतिरिक्त देरी समानांतर (या कंपित फैशन) में हो सकती है क्योंकि पारगमन का उपभोक्ता फेरबदल या मिश्रणों द्वारा लिखे गए रजिस्टरों को पढ़ना शुरू करता है। आउट-ऑफ-ऑर्डर निष्पादन पहले कुछ एफएमए या जो कुछ भी शुरू करने की अनुमति देता है, जबकि पिछले कुछ फेरबदल खत्म हो रहे हैं, लेकिन डैपास देरी की कोई श्रृंखला नहीं है, बस एक पर एक अतिरिक्त।
पीटर कॉर्ड्स

1
निकॉ जवाब! इंटेल 64-ia-32-आर्किटेक्चर-ऑप्टिमाइज़ेशन-मैनुअल, टेबल 2-3, स्काइलेक के लिए बाईपास देरी को सूचीबद्ध करता है, हो सकता है कि आप के लिए ब्याज की हो। हसवेल के लिए तालिका 2-8 काफी अलग दिखती है।
विम

1

प्रत्येक पंक्ति को एक स्तंभ के रूप में और प्रत्येक स्तंभ को एक पंक्ति के रूप में विचार करें .. का उपयोग करें j, i के बजाय i, j

डेमो: http://ideone.com/lvsxKZ

#include <iostream> 
using namespace std;

int main ()
{
    char A [3][3] =
    {
        { 'a', 'b', 'c' },
        { 'd', 'e', 'f' },
        { 'g', 'h', 'i' }
    };

    cout << "A = " << endl << endl;

    // print matrix A
    for (int i=0; i<3; i++)
    {
        for (int j=0; j<3; j++) cout << A[i][j];
        cout << endl;
    }

    cout << endl << "A transpose = " << endl << endl;

    // print A transpose
    for (int i=0; i<3; i++)
    {
        for (int j=0; j<3; j++) cout << A[j][i];
        cout << endl;
    }

    return 0;
}

1

बिना किसी ओवरहेड के ट्रांसपोज़िंग (वर्ग पूर्ण नहीं):

class Matrix{
   double *data; //suppose this will point to data
   double _get1(int i, int j){return data[i*M+j];} //used to access normally
   double _get2(int i, int j){return data[j*N+i];} //used when transposed

   public:
   int M, N; //dimensions
   double (*get_p)(int, int); //functor to access elements  
   Matrix(int _M,int _N):M(_M), N(_N){
     //allocate data
     get_p=&Matrix::_get1; // initialised with normal access 
     }

   double get(int i, int j){
     //there should be a way to directly use get_p to call. but i think even this
     //doesnt incur overhead because it is inline and the compiler should be intelligent
     //enough to remove the extra call
     return (this->*get_p)(i,j);
    }
   void transpose(){ //twice transpose gives the original
     if(get_p==&Matrix::get1) get_p=&Matrix::_get2;
     else get_p==&Matrix::_get1; 
     swap(M,N);
     }
}

इस तरह इस्तेमाल किया जा सकता है:

Matrix M(100,200);
double x=M.get(17,45);
M.transpose();
x=M.get(17,45); // = original M(45,17)

निश्चित रूप से मैं यहां स्मृति प्रबंधन से परेशान नहीं था, जो कि महत्वपूर्ण लेकिन अलग विषय है।


4
आपके पास अपने फ़ंक्शन पॉइंटर से एक ओवरहेड है जिसका पालन प्रत्येक तत्व एक्सेस के लिए किया जाना है।
user877329

1

यदि सरणियों का आकार पूर्व में जाना जाता है तो हम अपनी सहायता के लिए संघ का उपयोग कर सकते हैं। ऐशे ही-

#include <bits/stdc++.h>
using namespace std;

union ua{
    int arr[2][3];
    int brr[3][2];
};

int main() {
    union ua uav;
    int karr[2][3] = {{1,2,3},{4,5,6}};
    memcpy(uav.arr,karr,sizeof(karr));
    for (int i=0;i<3;i++)
    {
        for (int j=0;j<2;j++)
            cout<<uav.brr[i][j]<<" ";
        cout<<'\n';
    }

    return 0;
}

मैं C / C ++ में नया हूं, लेकिन यह प्रतिभाशाली दिखता है। क्योंकि यूनियन अपने सदस्यों के लिए साझा मेमोरी लोकेशन का उपयोग करता है, आप उस मेमोरी को अलग तरीके से पढ़ सकते हैं। इस प्रकार, आप एक नया सरणी आवंटन किए बिना एक ट्रांसपोज़्ड मैट्रिक्स प्राप्त करते हैं। क्या मैं सही हू?
Doğuş

1
template <class T>
void transpose( const std::vector< std::vector<T> > & a,
std::vector< std::vector<T> > & b,
int width, int height)
{
    for (int i = 0; i < width; i++)
    {
        for (int j = 0; j < height; j++)
        {
            b[j][i] = a[i][j];
        }
    }
} 

1
मुझे लगता है कि यह तेजी से होगा यदि आप दो छोरों का आदान-प्रदान करते हैं, तो पढ़ने की तुलना में छोटे कैश मिस दंड के कारण।
फोएगॉन

5
यह केवल एक वर्ग मैट्रिक्स के लिए काम करता है। एक आयताकार मैट्रिक्स एक पूरी अलग समस्या है!
नीलबी

2
सवाल सबसे तेज तरीके से पूछता है। यह सिर्फ एक तरीका है। क्या आपको लगता है कि यह तेज़ है, अकेले सबसे तेज़ चलो? बड़े मैट्रिसेस के लिए, यह कैश को थ्रैश करेगा और भयानक प्रदर्शन होगा।
एरिक पोस्टपिसिल

1
@ नील: आप ऐसा कैसे करते हैं?
एरिक पोस्टपिसिल

@EricPostpischil ओपी अपेक्षाकृत बड़े मैट्रिक्स के बारे में पूछ रहा है, इसलिए मुझे लगता है कि वे डबल मेमोरी आवंटित करने से बचने के लिए इसे "जगह में" करना चाहते थे। जब यह स्रोत और गंतव्य मैट्रिस के आधार पते को समान किया जाता है। रो और कॉलम सूचकांकों को फ़्लिप करके केवल वर्ग मैट्रिसेस के लिए काम करेगा। आयताकार मैट्रिक्स के लिए यह अधिकार प्राप्त करने के तरीके हैं लेकिन वे कुछ अधिक जटिल हैं।
नीलबेल

0

आधुनिक रैखिक बीजगणित पुस्तकालयों में सबसे आम संचालन के अनुकूलित संस्करण शामिल हैं। उनमें से कई में गतिशील सीपीयू प्रेषण शामिल है, जो प्रोग्राम निष्पादन समय (पोर्टेबिलिटी पर समझौता किए बिना) हार्डवेयर के लिए सबसे अच्छा कार्यान्वयन चुनता है।

यह आमतौर पर वेक्टर एक्सटेंशन आंतरिक कार्यों के माध्यम से अपने फंक्शनलिनो के मैनुअल अनुकूलन प्रदर्शन के लिए एक बेहतर विकल्प है। उत्तरार्द्ध आपके कार्यान्वयन को एक विशेष हार्डवेयर विक्रेता और मॉडल से जोड़ देगा: यदि आप एक अलग विक्रेता (जैसे पावर, एआरएम) या एक नए वेक्टर एक्सटेंशन (जैसे AVX512) पर स्वैप करने का निर्णय लेते हैं, तो आपको इसे फिर से लागू करने की आवश्यकता होगी उनमें से अधिकांश प्राप्त करें।

उदाहरण के लिए, MKL ट्रांसपोज़ेशन में BLAS एक्सटेंशन फ़ंक्शन शामिल है imatcopy। आप इसे अन्य कार्यान्वयन जैसे कि OpenBLAS में भी पा सकते हैं:

#include <mkl.h>

void transpose( float* a, int n, int m ) {
    const char row_major = 'R';
    const char transpose = 'T';
    const float alpha = 1.0f;
    mkl_simatcopy (row_major, transpose, n, m, alpha, a, n, n);
}

C ++ प्रोजेक्ट के लिए, आप आर्मडिलो C ++ का उपयोग कर सकते हैं:

#include <armadillo>

void transpose( arma::mat &matrix ) {
    arma::inplace_trans(matrix);
}

0

इंटेल mkl सुझाव देता है कि इन-प्लेस और आउट-ऑफ-प्लेस ट्रांसपोज़िशन / मैट्रिसेस की नकल करें। यहाँ प्रलेखन के लिए लिंक है । मैं जगह के कार्यान्वयन को दस से अधिक तेज़ी से लागू करने की कोशिश करूँगा और एमकेएल के नवीनतम संस्करण के प्रलेखन में कुछ गलतियाँ हैं।


-1

मुझे लगता है कि सबसे तेज़ तरीके को O (n ^ 2) से अधिक नहीं लेना चाहिए, इस तरह से आप सिर्फ O (1) स्थान का उपयोग कर सकते हैं:
ऐसा करने का तरीका जोड़े में स्वैप करना है क्योंकि जब आप एक मैट्रिक्स को स्थानांतरित करते हैं तो आप क्या करते हैं do है: M [i] [j] = M [j] [i], इसलिए M [i] [j] को टेम्प में स्टोर करें, फिर M [i] [j] = M [j] [i], और अंतिम चरण: M [j] [i] = अस्थायी। यह एक पास से हो सकता है इसलिए इसे O (n ^ 2) लेना चाहिए


2
M [i] [j] = M [j] [i] केवल तभी काम करेगा जब इसे वर्गाकार मैट्रिक्स होना चाहिए; अन्यथा यह एक सूचकांक अपवाद को फेंक देगा।
एंटनी थॉमस

-6

मेरा जवाब 3x3 मैट्रिक्स का ट्रांसपोज़्ड है

 #include<iostream.h>

#include<math.h>


main()
{
int a[3][3];
int b[3];
cout<<"You must give us an array 3x3 and then we will give you Transposed it "<<endl;
for(int i=0;i<3;i++)
{
    for(int j=0;j<3;j++)
{
cout<<"Enter a["<<i<<"]["<<j<<"]: ";

cin>>a[i][j];

}

}
cout<<"Matrix you entered is :"<<endl;

 for (int e = 0 ; e < 3 ; e++ )

{
    for ( int f = 0 ; f < 3 ; f++ )

        cout << a[e][f] << "\t";


    cout << endl;

    }

 cout<<"\nTransposed of matrix you entered is :"<<endl;
 for (int c = 0 ; c < 3 ; c++ )
{
    for ( int d = 0 ; d < 3 ; d++ )
        cout << a[d][c] << "\t";

    cout << endl;
    }

return 0;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.