Які самы хуткі спосаб Транспанаванне матрыцы ў C ++?

У мяне ёсць матрыца (адносна вялікі), што мне трэба перанесці. Напрыклад выкажам здагадку, што мая матрыца

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

Які самы хуткі спосаб зрабіць гэта?

60
@HighPerformanceMark: Я думаю, гэта залежыць, калі вы тады хочаце атрымаць доступ да матрыцы паўторна, каб радкі, якія маюць «транспонированная» сцяг ударыць вас цяжка.
дададзена аўтар Matthieu M., крыніца
Калі Intel ўласныя макрасы лічацца "C", што будзе _MM_TRANSPOSE() . :-)
дададзена аўтар Damon, крыніца
Гэта называецца «Транспанаванне». Паварот на 90 градусаў гэта зусім розныя паняцці.
дададзена аўтар Andy Prowl, крыніца
Акрамя таго, што гэта на самай справе не 90 градусаў гэта? Калі б гэта было першыя два радкі будзе т г а і п ч б .
дададзена аўтар Some programmer dude, крыніца
@HighPerformanceMark, калі матрыца захоўваецца ў выглядзе 2D масіва, замена індэксаў будзе <�я> не праца, калі лік слупкоў і радкоў не роўныя. Вы будзеце ў канчатковым выніку доступ да памяці за межамі масіва!
дададзена аўтар Marc Claesen, крыніца
Транспанаванне матрыцы сумна вядомы тым праблемы, якія ён выклікае ў схованках памяці. Калі ваш масіў досыць вялікі, што прадукцыйнасць транспонированного з'яўляецца значнай, і вы не можаце пазбегнуць пераносячы проста падаючы інтэрфейс з абменьваўся паказчыкамі, то лепшы варыянтам з'яўляецца выкарыстанне існуючай бібліятэчнай падпраграмы для транспазіцыя вялікіх матрыц. Эксперты ўжо зрабілі гэтую працу, і вы павінны яго выкарыстоўваць.
дададзена аўтар Eric Postpischil, крыніца
Існуе некаторая карысная інфармацыя ў
дададзена аўтар Eric Postpischil, крыніца
(Т. Е не квадрат) Калі матрыца можа быць прадстаўлена ў лінейнай памяці (1D масіў) і радкоў <> Стоўбцы, то гэты адказ можа мець некаторую дапамогу: stackoverflow.com/a/3514733/192510
дададзена аўтар NealB, крыніца
І самы хуткі спосаб не круціць яго, а проста памяняць парадак індэксаў пры доступе масіва.
дададзена аўтар High Performance Mark, крыніца
Незалежна ад таго, як хутка гэта, вы павінны атрымаць доступ да ўсіх элементаў матрыцы ў любым выпадку.
дададзена аўтар taocp, крыніца
Я знайшоў, яшчэ раз, больш хуткае рашэнне з выкарыстаннем SSE, блякаваньне цыкла і OpenMP. Я абнавіў свой адказ.
дададзена аўтар user2088790, крыніца
Так што я паглядзеў на гэта і абнавіў свой адказ. Я знайшоў рашэнне, якое значна хутчэй, чым тое, што я выкарыстоўваю з дапамогай блакавання завесы.
дададзена аўтар user2088790, крыніца
Аказваецца, цыкл пліткавым/блакаванне дапамагае для Транспанаванне, а таксама. stackoverflow.com/questions/5200338/…
дададзена аўтар user2088790, крыніца

8 адказы

Гэта добры пытанне. Ёсць шмат прычын вы хочаце на самай справе транспонировать матрыцу ў памяці, а не проста памяняць каардынаты, напрыклад, у матрычнай памнажэньні і гауссовым размыццём.

Перш за ўсё дазвольце мне пералічыць адзін з функцый, якія я выкарыстоўваю для Транспанаванне (<�моцны> EDIT: гл канец майго адказу, дзе я знайшоў значна хутчэй рашэнне )

void transpose(float *src, float *dst, const int N, const int M) {
    #pragma omp parallel for
    for(int n = 0; n

Зараз давайце паглядзім, чаму транспонированное карысна. Разгледзім матрыцу множання C = A * B. Мы маглі б зрабіць гэта такім чынам.

for(int i=0; i

Такім чынам, аднак, будзе мець шмат промахаў кэша. Значна хутчэй, рашэнне ўзяць Транспанаванне У першым

transpose(B);
for(int i=0; i

Множанне матрыц O (N ^ 3) і Транспанаванне O (N ^ 2), таму прымаць Транспанаванне павінны мець нязначнае ўплыў на час вылічэнні (пры вялікіх п ). У матрыцы цыкл множання пліткавае нават больш эфектыўна, чым прымаць Транспанаванне, але гэта нашмат складаней.

Я хацеў бы я ведаў, што больш хуткі спосаб зрабіць Транспанаванне (<�моцны> Edit: я знайшоў больш хуткае рашэнне, убачыць канец майго адказу ). Калі Haswell/AVX2 выходзіць праз некалькі тыдняў ён будзе мець функцыю збору. Я не ведаю, калі гэта будзе карысна у гэтым выпадку, але я мог бы малюнак сабраць калонку і выпісваючы радок. Можа быць, гэта зробіць транспонированная непатрэбным.

Для гауссовского размыцця, што вы робіце мазок па гарызанталі, а затым нашмараваць вертыкальна. Але размазванне па вертыкалі мае праблемы кэша, так што вы робіце

Smear image horizontally
transpose output 
Smear output horizontally
transpose output

Here is a paper by Intel explaining that http://software.intel.com/en-us/articles/iir-gaussian-blur-filter-implementation-using-intel-advanced-vector-extensions

І, нарэшце, што я на самой справе рабіць у матрычнай множанне (і ў гауссовой размыцця) не прымаць дакладна Транспанаванне, але ўзяць Транспанаванне у шырыню пэўнага вектара памеру (напрыклад, 4 або 8 для SSE/AVX). Вось функцыя, я выкарыстоўваю

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

<�Моцны> EDIT:

Я паспрабаваў некалькі функцый, каб знайсці самае хуткае Транспанаванне для вялікіх матрыц. У рэшце рэшт, самы хуткі вынік заключаецца ў выкарыстанні цыклу блакавання з кодам <> BLOCK_SIZE = 16 (<�моцны> Edit: я знайшоў больш хуткае рашэнне з выкарыстаннем SSE і завесы блакавання - гл ніжэй ). Гэты код працуе для любога NXM матрыцы (г.зн. матрыца не павiнна быць квадратнай).

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

Значэння 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);

For 3000x1001 this returns ldb = 3008 and lda = 1008

<�Моцны> EDIT:

Я знайшоў яшчэ больш хуткае рашэнне з выкарыстаннем 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 : n;
            int max_j2 = j+block_size < m ? j + block_size : m;
            for(int i2=i; i2
108
дададзена
@ Ulyssis2 Наіўныя множання матрыц з'яўляюцца найбольш вызначана O (N ^ 3), і, наколькі я ведаю, вылічыце ядра рэалізаваць наіўны алгарытм (я думаю, што гэта адбываецца таму, што штрасэ заканчвае тым, што робіць спосаб больш аперацыі (дапаўненні), які дрэнна, калі вы можаце зрабіць хуткія прадукты, але я магу памыляцца). Гэта адкрытая праблема, ці можа быць множанне матрыц Аб (п ^ 2) ці не.
дададзена аўтар étale-cohomology, крыніца
У выпадку, калі хто-небудзь хоча ведаць, хто напісаў гэты адказ ён быў І. кінуў SO разы, атрымалі над ім, і вярнуўся.
дададзена аўтар Z boson, крыніца
@ Ulyssis2 Гэта O (N ^ 3), калі вы не выкарыстоўваеце штрасэ матрычнага множання (О (п ^ 2,8074)). user2088790: Гэта вельмі добра зроблена. Маючы гэта на маёй асабістай калекцыі. :)
дададзена аўтар saurabheights, крыніца
Добры стрэл, але я не ўпэўнены, што 'Матрыца множання O (N ^ 3)', я думаю, што гэта O (N ^ 2).
дададзена аўтар ulyssis2, крыніца

Гэта будзе залежаць ад вашага прыкладання, але ў цэлым самы хуткі спосаб транспонировать матрыцу будзе інвертаваць вашыя каардынаты, калі вы робіце погляд уверх, то вы не павінны фактычна перамясціць дадзеныя.

37
дададзена
@beaker: ён павінен рабіць з кэшаваннем на ўзроўні працэсара (калі выказаць здагадку, што матрыца ўяўляе сабой адзін вялікі камяк памяці), радкі кэша затым эфектыўныя лініі матрыцы, і папераджальныя можа прынесці наступныя некалькі радкоў. Пры пераключэнні доступу, кэш CPU/префетчер яшчэ парадкова працаваць у той час як доступ слупок за слупком, падзенне прадукцыйнасці можа быць драматычным.
дададзена аўтар Matthieu M., крыніца
@taocp У прынцыпе, вам трэба будзе нейкі сцяг, каб паказаць, што пераносіцца, а затым запытаць напрыклад (I, J) будзе адлюстроўвацца ў (J, I)
дададзена аўтар Shafik Yaghmour, крыніца
Гэта вельмі добра, калі гэта невялікая матрыца, ці вы толькі чытаць з яго адзін раз. Аднак, калі транспонированная матрыца вялікая і павінна быць паўторна шмат разоў, вы ўсё яшчэ можаце захаваць хутка транспонированную версію, каб атрымаць лепшы ўзор доступу да памяці. (+1, дарэчы)
дададзена аўтар Agentlien, крыніца
@beaker Калі ў вас ёсць вялікая матрыца, розныя радкі/слупкі могуць займаць розную кэш-лінію/старонку. У гэтым выпадку, вы хочаце перабраць элементы такім чынам, што вы атрымліваеце доступ суседніх элементаў адзін за адным. У адваротным выпадку, гэта можа прывесці да кожнага элементу доступу становіцца промахі кэша, які цалкам знішчае прадукцыйнасць.
дададзена аўтар Agentlien, крыніца
@Agentlien Ах, добры момант.
дададзена аўтар beaker, крыніца
@Agentlien: Чаму A [J] [я] быць павольней, чым A [I] [J]?
дададзена аўтар beaker, крыніца
Па каардынатах інвертаваць, вы маеце на ўвазе перамыкач восі х і у?
дададзена аўтар taocp, крыніца
Акрамя таго, калі вы перадаеце матрыцу паміж прыкладаннямі, якія не зьяўляюцца слупкамі, ці не патрабуюцца як радок-мажор, транспазіцыя.
дададзена аўтар Jack Wasey, крыніца

мой адказ транспонируется з матрыцы 3х3

 #include

#include


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

cin>>a[i][j];

}

}
cout<<"Matrix you entered is :"<< 3 ; e++ )

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

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


    cout << endl;

    }

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

    cout << endl;
    }

return 0;
}
5
дададзена

Некаторыя падрабязнасці аб транспазіцыя 4x4 квадратных паплаўка (я разгледжу 32-разраднае цэлы лік пазней) матрыц з x86 апаратнага забеспячэння. Гэта карысна, каб пачаць тут, каб транспонировать вялікія квадратныя матрыцы, такія як 8x8 або 16x16.

_MM_TRANSPOSE4_PS(r0, r1, r2, r3) is implemented differently by different compilers. GCC and ICC (I have not checked Clang) use unpcklps, unpckhps, unpcklpd, unpckhpd whereas MSVC uses only shufps. We can actually combine these two approaches together like this.

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 сумесяў. Пры гэтым выкарыстоўваецца больш за 2 інструкцыі, чым рэалізацыі GCC, МТП і MSVC. Перавага складаецца ў тым, што ён зніжае ціск порта, які можа мець перавагу ў некаторых абставінах. У цяперашні час усё змешвае і распакоўвае могуць ісці толькі адзін канкрэтны порт, у той час як сумесі могуць ісці ні да аднаго з двух розных партоў.

Я паспрабаваў з дапамогай 8 ператасоўкі як MSVC і пераўтварэнні, што ў 4 тасуе + 8 сумесяў, але гэта не спрацавала. Я да гэтага часу даводзілася выкарыстоўваць 4 распакоўвае.

Я выкарыстаў тую ж самую тэхніку для 8x8 флоат транспонированной (гл ў канцы гэтага адказу). https://stackoverflow.com/a/25627536/2542702 . У гэтым адказе я ўсё яшчэ павінен быў выкарыстаць 8 распакоўвае, але я абазнаны і каб пераўтварыць 8 ператасоўкі ў 4 змешвае і 8 сумесяў.

Для 32-разрадных цэлых лікаў не існуе нічога падобнага shufps (для 128-бітавых змешвае з AVX512 выключэннем), таму ён можа быць рэалізаваны толькі з распакоўвае, якія я не думаю, можа быць пераўтварыць у сумесі (эфектыўна). З AVX512 vshufi32x4 эфектыўна дзейнічае як shufps для 128-бітавых палос 4 цэлых лікаў замест 32-бітнай, за выключэннем плавае так што гэтая ж тэхніка можа быць магчыма, з vshufi32x4 у некаторых выпадках. З рыцарамі Пасадкавыя ператасоўкі ў чатыры разы павольней (прапускная здольнасць), чым сумесі.

4
дададзена
@PeterCordes, мне трэба прагледзець змены дамена зноў. Ці ёсць які-небудзь стол (можа быць, адказ на SO), што рэзюмэ няўстойкі змены дамена для Core2-Skylake? У любым выпадку, я даў больш думак пра гэта. Цяпер я разумею, чаму Wim і вы захавалі згадваючы vinsertf64x4 ў маім 16x16 транспазіцыя адказ замест vinserti64x4 . Калі я чытаю тое піша матрыцу, то гэта, вядома, не мае значэння, калі я выкарыстоўваю дамен з якая плавае коскі або цэлалікавых дамен, так як Транспанаванне толькі перамяшчэннем дадзеных.
дададзена аўтар Z boson, крыніца
Вы можаце выкарыстоўваць shufps на цэлалікавых дадзеных. Калі вы робіце шмат ператасоўкі, гэта можа быць варта ўсё гэта рабіць у галіне FP для shufps + blendps , асабліва калі ў вас няма аднолькава эфектыўны AVX2 vpblendd даступны. Акрамя таго, на Intel SnB сямейства апаратных сродкаў, няма ніякай дадатковай затрымкі байпас для выкарыстання shufps паміж цэлымі інструкцыямі, як paddd . (Існуе затрымка байпас для змешвання blendps з paddd , згодна з тэставання SNB Agner FOG, хоць.)
дададзена аўтар Peter Cordes, крыніца
Agner ў спіс табліц даменаў на-інструкцыі для Core2 і Nehalem (і AMD я думаю), але не SnB сям'і. microarch кіраўніцтва Agner проста ёсць пункт аб тым, што гэта да 1в і часта 0 на SnB, з некаторымі прыкладамі. кіраўніцтва па аптымізацыі Intel мае табліцу, я думаю, але я не спрабаваў звяртаў увагі, так што я не памятаю, колькі дэталяў ён мае. Я ўспамінаю, што не будучы цалкам відавочна, што катэгорыя дадзеная інструкцыя будзе ст.
дададзена аўтар Peter Cordes, крыніца
Нават калі вы не проста пісаць назад у памяць, гэта толькі адзін дадатковыя гадзіны для ўсёй Транспанаванне. Дадатковая затрымка для кожнага аперанда можа быць адбываецца паралельна (або шахматны парадак), як спажывец транспонированного пачынае чытаць рэгістры, напісаныя тасуюць або сумесі. Пазачарговага выкананне дазваляе першыя некалькі FMAs або незалежна ад таго, каб пачаць у той час як апошнія некалькі тасуе сканчаюць, але няма ніякай ланцуга затрымкі dypass, толькі дадатковай не больш за адзін.
дададзена аўтар Peter Cordes, крыніца
Nicw адказаць! Intel 64-IA-32-архітэктура аптымізацыя-кіраўніцтва, табліца 2-3, пералічаныя затрымкі абыходных для Skylake, можа быць, гэта для вас цікавасці. Табліца 2-8 для Haswell выглядае зусім па-іншаму.
дададзена аўтар wim, крыніца
Я думаю, што на Skylake vinsertf64x4 і vinserti64x4 ўзаемазаменныя. Я не была прычына згадаць адзін ці іншы. Я проста думаў 64x4 біт дадзеных.
дададзена аўтар wim, крыніца
template 
void transpose( std::vector< std::vector > a,
std::vector< std::vector > 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
дададзена
@NealB Не, для прастакутных матрыц
дададзена аўтар David Heffernan, крыніца
@raxman: Магчыма, вы звярнуліся да неналежнага асобе ці няправільна заяву аб тым, што код "не няправільна».
дададзена аўтар Eric Postpischil, крыніца
@NealB: Гэтыя крытычныя заўвагі непрыдатныя да гэтага коду. Гэты код не з'яўляецца няправільным для неквадратных матрыц.
дададзена аўтар Eric Postpischil, крыніца
@NealB: Як вы гэта?
дададзена аўтар Eric Postpischil, крыніца
Пытанне задае самы хуткі спосаб. Гэта проста спосаб. Што прымушае вас думаць, што гэта хутка, не кажучы ўжо хутчэй? Для вялікіх матрыц, гэта трэш кэша і мае жудасную прадукцыйнасць.
дададзена аўтар Eric Postpischil, крыніца
Гэта працуе толькі для квадратнай матрыцы. Прастакутная матрыца цэлая іншая праблема!
дададзена аўтар NealB, крыніца
@EricPostpischil ОП просіць аб параўнальна вялікі матрыцы, так што я мяркую, што яны хацелі зрабіць гэта «на месцы», каб пазбегнуць вылучэння ў два разы больш памяці. Калі гэта робіцца базавы адрас крыніцы і прызначэння матрыц аднолькавыя. Транспанаванне гартаць радкі і слупкі індэксаў будзе працаваць толькі для квадратных матрыц. Ёсць спосабы, каб атрымаць гэта права для прастакутных матрыц, але яны некалькі складаней.
дададзена аўтар NealB, крыніца
дададзена аўтар Rachel Gallen, крыніца
Я хацеў бы думаць, што было б хутчэй, калі б вы абмяняць дзве завесы, з-за меншага кэша промахі штрафу ў пісьмовай форме, чым чытанне.
дададзена аўтар phoeagon, крыніца
О, ты маеш рацыю. Я меў на ўвазе звярнуцца @NealB.
дададзена аўтар user2088790, крыніца
Гэты код выдатна падыходзіць для неквадратных матрыц (хоць і не вельмі аптымальна). Я думаю, што @EricPostpischil думае пра alogirthm для маналітнага Транспанаванне. Гэта значна складаней en.wikipedia.org/wiki/….
дададзена аўтар user2088790, крыніца

Разгледзім кожную радок у якасці слупка, і кожны слупок у якасці радкі .. выкарыстоўваць J, I замест I, J

demo: http://ideone.com/lvsxKZ

#include  
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
дададзена

Я думаю, што самы хуткі спосаб не прымаць больш, чым O (N ^ 2) і, такім чынам, вы можаце выкарыстоўваць толькі O (1) прасторы:
спосаб зрабіць гэта, каб памяняць месцамі ў парах, таму што, калі вы транспонировать матрыцу, тое, што вы робіце: M [я] [J] = M [J] [я], так што крама M [я] [J] ў тэмп, то М [I] [J] = M [J] [I], і апошні крок: М [J] [I] = тэмп. гэта можа быць зроблена за адзін праход, так што варта прымаць O (N ^ 2)

1
дададзена
М [I] [J] = M [J] [I] будзе працаваць толькі тады, калі гэта павінна было быць квадратнай матрыцай; інакш гэта будзе згенеравана выключэнне азначніка.
дададзена аўтар Antony Thomas, крыніца

пераносячы без якіх-небудзь накладных расходаў (клас не поўны):

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)

я, вядома, не затлумляцца з кіраваннем памяццю тут, што вельмі важна, але іншая тэма.

1
дададзена
У вас ёсць накладныя выдаткі ад паказальніка функцыі, які павінен прытрымлівацца для кожнага доступу элемента.
дададзена аўтар user877329, крыніца