{"id":24375881,"url":"https://github.com/csvancea/matrix-multiply","last_synced_at":"2026-04-20T18:33:25.421Z","repository":{"id":130856213,"uuid":"360931090","full_name":"csvancea/matrix-multiply","owner":"csvancea","description":"Assignment: optimize memory/cache accesses as much as possible for a matrix multiplication. The optimized approach is then compared to BLAS (dedicated library for vector and matrix operations)","archived":false,"fork":false,"pushed_at":"2021-04-25T19:38:44.000Z","size":64,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-12T20:17:17.801Z","etag":null,"topics":["cache","matrix-multiplication","optimizations"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/csvancea.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-04-23T15:38:55.000Z","updated_at":"2022-07-18T17:57:50.000Z","dependencies_parsed_at":null,"dependency_job_id":"119ca529-1043-475c-816d-33760e31f5ba","html_url":"https://github.com/csvancea/matrix-multiply","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/csvancea/matrix-multiply","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csvancea%2Fmatrix-multiply","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csvancea%2Fmatrix-multiply/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csvancea%2Fmatrix-multiply/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csvancea%2Fmatrix-multiply/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/csvancea","download_url":"https://codeload.github.com/csvancea/matrix-multiply/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csvancea%2Fmatrix-multiply/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32059801,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-20T11:35:06.609Z","status":"ssl_error","status_checked_at":"2026-04-20T11:34:48.899Z","response_time":94,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","matrix-multiplication","optimizations"],"created_at":"2025-01-19T05:58:30.493Z","updated_at":"2026-04-20T18:33:25.415Z","avatar_url":"https://github.com/csvancea.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Matrix-Multiply - Cosmin-Răzvan VANCEA - 333CA\n\n## Implementare\n\n### Varianta (C)BLAS\n\nAm impărțit calculul în următoarele subcalcule:\n\n* `C = A' x A`\n  * Inițial pun în C rezultatul calcului de mai sus.\n  * Pentru efectuarea calculului am folosit funcția `DTRMM` în forma:\n  `C := alpha*op( A )*A`, cu `alpha = 1.0`, `op(A) = A**T` și `A = UPPER TRIANGULAR`\n\n* `AB = A x B`\n  * Pentru efectuarea calculului am folosit funcția `DTRMM` în forma:\n  `AB := alpha*op( A )*B`, cu `alpha = 1.0`, `op(A) = A` și `A = UPPER TRIANGULAR`\n\n* `C = [(A x B) x B'] + (A' x A) = AB x B' + C`\n  * Pentru efectuarea calculului am folosit funcția `DGEMM` în forma:\n  `C := alpha*op( AB )*op( B ) + beta*C`, cu `alpha = 1.0`, `op(AB) = AB`,\n  `beta = 1.0`și `op(B) = B**T`\n\n#### Rezultate pe coada ibm.nehalem:\n\n```\nRun=./tema2_blas: N=400: Time=0.058755  OK\nRun=./tema2_blas: N=600: Time=0.131540\nRun=./tema2_blas: N=800: Time=0.292576  OK\nRun=./tema2_blas: N=1000: Time=0.490025\nRun=./tema2_blas: N=1200: Time=0.862272 OK\nRun=./tema2_blas: N=1400: Time=1.327101\nRun=./tema2_blas: N=1600: Time=1.969486\nRun=./tema2_blas: N=1800: Time=2.794288\nRun=./tema2_blas: N=2000: Time=3.809342\n```\n\n### Varianta neoptimizată\n\nAm impărțit calculul în următoarele subcalcule:\n\n* `AtA = A' x A`\n  * Cum A este superior triunghiulară, rezultă că A' este inferior triunghiulară\n  * Astfel, pot să reduc numărul de înmulțiri ignorând zerourile din ambele matrici\n  * Pentru a face asta, k ia valori în intervalul: `k = [0 .. min(i, j)]`\n\n* `BBt = B x B'`\n  * Nu se știe nimic despre B, așadar se folosește algoritmul clasic de înmulțire\n\n* `ABBt = A x (B x B') = A x BBt`\n  * Se ignoră zerourile din partea inferioară a lui A (`k = [i .. N]`)\n\n* `C = [A x (B x B')] + (A' x A) = ABBt + AtA`\n  * Se însumează rezultatele temporare și se salvează în totul în C\n\n#### Rezultate pe coada ibm.nehalem:\n\n```\nRun=./tema2_neopt: N=400: Time=1.135183    OK\nRun=./tema2_neopt: N=600: Time=3.470950\nRun=./tema2_neopt: N=800: Time=8.269319    OK\nRun=./tema2_neopt: N=1000: Time=16.031574\nRun=./tema2_neopt: N=1200: Time=27.859262  OK\nRun=./tema2_neopt: N=1400: Time=45.311760\nRun=./tema2_neopt: N=1600: Time=74.682930\nRun=./tema2_neopt: N=1800: Time=103.854233\nRun=./tema2_neopt: N=2000: Time=145.613525\n```\n\n### Varianta optimizată\n\nAm impărțit calculul în următoarele subcalcule:\n\n* `At = A'`\n  * Precalculez A' deoarece mai departe va trebui să fac calculul `A' x A`,\n  iar după cum s-a observat și pe implementarea neoptimizată, accesele la matrici\n  în varianta aceasta sunt nesecvențiale (`AtA[i][j] += A[k][i] * A[k][j]`).\n  * Cum A este superior triunghiulară, rezultă că A' este inferior triunghiulară.\n  * Formulă de calcul a transpusei este: `At[j][i] = A[i][j]`\n  * Optimizări:\n    1. Se sare peste zerourile de sub diagonala matricii A (`j = [0 .. i]`)\n    2. Adresarea elementelor se face prin pointer arithmetic\n    3. Din formula de calcul se observă că accesul la memorie este secvențial\n    pentru A, dar nesecvențial pentru At (unlucky)\n\n* `C = A' x A = A' x (A')' = At * At'`\n  * Noua formulă de calcul este: `C[i][j] += At[i][k] * At[j][k]`\n  * Optimizări:\n    1. Se sare peste zerourile din ambele matrici\n    2. Adresarea elementelor se face prin pointer arithmetic\n    3. Din formula de calcul se observă că accesul la memorie este optim\n    (C - constant, At - secvențial, At - secvențial)\n    4. Se folosesc regiștri pentru sume parțiale\n    5. Se înlocuiește apelul de funcție `min` cu codul inline.\n\n* `BBt = B x B'`\n  * Formula de calcul este: `BBt[i][j] += B[i][k] * B[j][k]`\n  * Calcul este similar celui anterior, singura diferență fiind că B nu este matrice\n  superior triunghiulară. Din această cauză nu se ignoră înmulțiri.\n  * Optimizări:\n    * (-) Ignorarea zerourilor nu se mai poate face\n    * (+) În schimb, se garantează că N este multiplu de 40, deci implicit 8 și\n    se face loop unrolling la cea mai din interior buclă\n\n* `ABBt = A x (B x B') = A x BBt`\n  * Formula de calcul este: `ABBt[i][j] += A[i][k] * BBt[k][j]`\n  * Optimizări:\n    1. Se sare peste zerourile din matricea A\n    2. Adresarea elementelor se face prin pointer arithmetic\n    3. Se observă din formulă că accesul la BBt este nesecvențial; se impune\n    o reordonare a buclelor astfel: i - k - j\n    4. După reordonare, accesul la memorie este optim (ABBt - secvențial,\n    A - constant, BBt - secvențial)\n    4. Se folosesc regiștri pentru constante\n    5. Se face loop unrolling la cea mai din interior buclă\n\n* `C = [A x (B x B')] + (A' x A) = ABBt + C`\n  * Se adună la C(= A' x A) rezultatul calculului anterior.\n  * Formula de calcul este: `C[i][j] += ABBt[i][j]`\n  * Optimizări:\n    1. Adresarea elementelor se face prin pointer arithmetic\n    2. Accesul la memorie este optim (secvențial)\n    3. Se face loop unrolling\n\n\n#### Note generale:\n\nFolosesc regiștri pentru aproape toate variabilele, singurele variabile alocate\npe stack fiind cele accesate rar (base pointerul pentru matricele temporare).\nCu ajutorul unui decompiler am confirmat că se respectă keyword-ul `register`,\nsingurele variabile aruncate pe stivă fiind următoarele:\n\n```c\ndouble *A; // [rsp+10h] [rbp-60h]\ndouble *ABBt; // [rsp+20h] [rbp-50h]\ndouble *BBt; // [rsp+28h] [rbp-48h]\ndouble *At; // [rsp+30h] [rbp-40h]\ndouble *C; // [rsp+38h] [rbp-38h]\n```\n\n#### Rezultate pe coada ibm.nehalem:\n\n(tbf, testul N=1200 e hit or miss; alternează între ~4.05s și ~4.30s)\n\n```\nRun=./tema2_opt_m: N=400: Time=0.161150   OK\nRun=./tema2_opt_m: N=600: Time=0.541214\nRun=./tema2_opt_m: N=800: Time=1.270233   OK\nRun=./tema2_opt_m: N=1000: Time=2.413441\nRun=./tema2_opt_m: N=1200: Time=4.055645  OK\nRun=./tema2_opt_m: N=1400: Time=6.650734\nRun=./tema2_opt_m: N=1600: Time=10.012427\nRun=./tema2_opt_m: N=1800: Time=14.430511\nRun=./tema2_opt_m: N=2000: Time=19.866138\n\u003c\u003c\u003c Bonus=10p \u003e\u003e\u003e\n```\n\n## Analiză valgrind\n\nNu există memory leak-uri. Rezultatele rulărilor se află în `analysis/`.\n\n## Analiză cachegrind\n\nPrimul lucru care iese în evidență este numărul de instrucțiuni (5.7mld pentru\n`neopt`, 1.2 mld pentru `opt_m` și doar 200 mil pentru `BLAS`). Totuși, numărul\nde miss-uri în cache-ul pentru instrucțiuni este aproape inexistent în toate\ncele 3 implementări.\n\nUrmătorul fapt observat este accesul la memorie - `opt_m` accesează de 10 ori\nmai puține date din memorie față de varianta neoptimizată. Acest salt uriaș\npoate fi datorat optimizării constantelor și folosirii a cât mai multe registre\nîn loc de variabile alocate pe stivă.\n\nDeși procentual în cazul implementării optimizate sunt mai multe missuri, acestea\nsunt în valoare absolută cu mult mai puține față de varianta neoptimizată, fapt\ndatorat optimizărilor de reordonare a buclelor pentru a avea acces secvențial.\n\nUltimul lucru observat este numărul de branch-uri mai mic în cazul implementării\noptimizate (loop unrolling).\n\nÎn urma analizării executabilului `BLAS` se observă că acesta exploatează foarte\nbine accesul la memorie, având atât procentual, cât și în valori absolute mult\nmai puține miss-uri, branch-uri și mispredicts față de celelalte 2 implementări.\n\nRezultatele rulărilor se află în `analysis/`.\n\n## Analiză comparativă\n\n![Grafic](analysis/comparison.png)\n\nÎn urma analizei timpilor de execuție a celor 3 implementări se observă că\npentru matrici mici (N \u003c 800), `opt_m` și `BLAS` au timpi apropiați, însă\npentru matrici mai mari de atât, implementarea cu `BLAS` se execută mai\nrapid (medie: de 5x mai rapid, însă conform evoluției graficului estimez că\npentru valori N \u003e 2000 această diferență între implementări va crește).\n\nImplementarea neoptimizată nu se poate compara nici cu `opt_m`, nici cu `BLAS`\n(mai ales). Spre exemplu, în timpul în care neopt calculează rezultatul pentru\no matrice cu N=400, `BLAS` poate face același calcul cu N=1400, iar această\ndiferență crește pe măsură ce N crește. În medie, `BLAS` este de 32 de ori mai\nrapid decât `neopt`. În raport cu `opt_m`, implementarea neoptimizată este în\nmedie de 7 ori mai lentă.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcsvancea%2Fmatrix-multiply","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcsvancea%2Fmatrix-multiply","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcsvancea%2Fmatrix-multiply/lists"}