WebHU - Programozási kérdések és válaszok

Kicsit tömörítő teljesítmény a Julia vs Python játékban

Ez a kérdés (Paralellezze a kódot, amely bitenkénti műveletet végez) nagyon jó választ kaptam és rendkívül hatékony kódot, amit csak C kóddal tudtam párosítani. Ez arra késztetett, hogy megpróbáljam legalább a Julia Python-kódot megfeleltetni.

A következő Python-kód 0,64 másodpercet vesz igénybe, míg a C kód 0,27 másodpercet vesz igénybe a bitek előjel nélküli egész számokba való becsomagolásához.

import numpy as np
import numba as nb
import time
colm = int(200000/8)
rows = 10000
cols = int(colm*8)
AU = np.random.randint(2,size=(rows, cols),dtype=np.int8)
A = np.empty((rows,colm), dtype=np.uint8)

@nb.njit('void(uint8[:,:],int8[:,:])', parallel=True)
def compute(A, AU):
    for i in nb.prange(A.shape[0]):
        for j in range(A.shape[1]):
            offset = j * 8
            res = AU[i,offset] << 7
            res |= AU[i,offset+1] << 6
            res |= AU[i,offset+2] << 5
            res |= AU[i,offset+3] << 4
            res |= AU[i,offset+4] << 3
            res |= AU[i,offset+5] << 2
            res |= AU[i,offset+6] << 1
            res |= AU[i,offset+7]
            A[i,j] = res

start_time = time.time()

compute(A, AU)

end_time = time.time()
print(end_time - start_time)

Az alábbiakban bemutatjuk a különféle sikertelen kísérleteket, hogy megfeleljenek ennek a teljesítménynek Juliában:

using Random
colm = 200000÷8
rows = 10000
cols = colm*8

AU = zeros(UInt8, (rows, cols))

rand!(AU)
AU .&= 0x01

A = BitArray(undef, rows, cols)
B = zeros(UInt8, (rows, colm))

function compute1(A, AU)
    A[:,:] .= AU .== 1
end

function compute2(A, AU)
    for i in 1:size(A)[2]
        start_col = (i-1) << 3
        A[:, i] .=  AU[:, start_col + 1]        .| 
                   (AU[:, start_col + 2] .<< 1) .|
                   (AU[:, start_col + 3] .<< 2) .|
                   (AU[:, start_col + 4] .<< 3) .|
                   (AU[:, start_col + 5] .<< 4) .|
                   (AU[:, start_col + 6] .<< 5) .|
                   (AU[:, start_col + 7] .<< 6) .|
                   (AU[:, start_col + 8] .<< 7)        
    end
end

function compute3(A, AU)
    for i in 1:size(A)[2]
        start_col = (i-1) << 3
        A[:, i] .|=  AU[:, start_col + 1] 
        A[:, i] .|=  AU[:, start_col + 2] .<< 1
        A[:, i] .|=  AU[:, start_col + 3] .<< 2
        A[:, i] .|=  AU[:, start_col + 4] .<< 3
        A[:, i] .|=  AU[:, start_col + 5] .<< 4
        A[:, i] .|=  AU[:, start_col + 6] .<< 5
        A[:, i] .|=  AU[:, start_col + 7] .<< 6
        A[:, i] .|=  AU[:, start_col + 8] .<< 7
    end
end

function compute4(A, AU)
    for i in 0:7
        au_columns = [((j-1) << 3) + i + 1 for j in 1:size(A)[2]] 
        A[:, :] .|=  AU[:, au_columns] .<< i
    end
end

@time compute1(A, AU)
@time compute2(B, AU)
@time compute3(B, AU)
@time compute4(B, AU)

Kimenet:

  6.128301 seconds (553.01 k allocations: 30.192 MiB, 2.22% compilation time)
  3.640022 seconds (1.97 M allocations: 1.984 GiB, 3.05% gc time, 12.43% compilation time)
  2.956211 seconds (1.44 M allocations: 3.842 GiB, 3.73% gc time, 19.24% compilation time)
  6.720456 seconds (946.91 k allocations: 3.776 GiB, 2.40% gc time, 4.68% compilation time)

A különböző módszerek 3-6 másodpercig tartanak. Nem tudja, hogyan lehetne javítani a teljesítményen, hogy legalább a Python/Numba megfeleljen

23.04.2021

  • Gyors megjegyzés: a Julia-ban történő szeletelés egy másolatot hoz létre, így minden alkalommal, amikor megteszi a A[:, ...] műveletet, egy teljesen új tömböt foglal le. Nézetek használata: docs.julialang.org/en/ v1/manual/performance-tips/ 23.04.2021
  • A numba kód nagyon optimalizált és párhuzamos. Csodálom, hogy sikerült párosítani a teljesítményt egy egyszerű C kóddal. Valami hasonlót kell tennie, mint nb.prange Juliában, hogy közelebb kerüljön. 23.04.2021
  • Igen, a nézetek segítenek. compute4 nézetek nélkül: 3.545348 seconds (400.00 k allocations: 3.791 GiB, 16.12% gc time); nézetekkel: 1.850615 seconds (200.00 k allocations: 1.895 GiB, 14.13% gc time). Figyelje meg a kiosztások számának csökkenését. 23.04.2021
  • Számomra compute2 @views nélkül 2.898168 seconds (1.98 M allocations: 1.985 GiB, 5.86% gc time, 10.84% compilation time), @views-vel 0.363232 seconds (667.67 k allocations: 32.487 MiB, 4.75% gc time, 50.73% compilation time). Ezenkívül jobban használja a github.com/JuliaCI/BenchmarkTools.jl webhelyet a Julia-kód összehasonlításához, és interpoláljon globális változók: ``` julia› @btime compute2($B, $AU); 172,111 ms (0 kiosztás: 0 bájt) ``` 23.04.2021
  • @Megalng Ez most párosítva, sőt továbbfejlesztve is csak a @views makróval a compute2 függvényben. A A = Bool.(A2) használata hasonló teljesítményt nyújt. Lásd @VincentYu válaszát. 23.04.2021
  • @ForceBru A @views makró használatával sikerült. 23.04.2021

Válaszok:


1

Miért nem próbálja úgy kinézni a Julia-kódot, mint a Python-kód, ha össze akarja hasonlítani a teljesítményüket? Tehát valami ilyesmi:

rowm = 200000 ÷ 8
cols = 10000
rows = rowm * 8

AU = rand(Int8.(0:1), rows, cols)
A = zeros(UInt8, rowm, cols)

function compute!(A, AU)
    for i in 1:size(A, 2)
        Threads.@threads for j in 1:size(A, 1)
            offset = (j-1) * 8 + 1
            res =  AU[offset  , i] << 7
            res |= AU[offset+1, i] << 6
            res |= AU[offset+2, i] << 5
            res |= AU[offset+3, i] << 4
            res |= AU[offset+4, i] << 3
            res |= AU[offset+5, i] << 2
            res |= AU[offset+6, i] << 1
            res |= AU[offset+7, i]
            A[j, i] = res % UInt8
        end
    end
end

Vegye figyelembe, hogy a Julia oszlopfő, ezért az indexek sorrendjét fel kell cserélni. És kifejezetten több szállal kell elindítania a juliát, hogy a többszálú legyen hasznos (julia -t8 a Julia 1.6-on).

23.04.2021
  • Kevésbé szeretek utánozni, mint kitalálni, hogyan győzzem le a legjobb Pythont. 23.04.2021
  • Kipróbáltam a kódot a colm=200000/8 és a rows=30000 használatával. A teljes futási idő 48,0 másodperc vs. 11,7 másodperc a @views makróval módosított compute2() függvény használatával. Bár a julia --threads 4-et adtam meg, a többszálú feldolgozás valamilyen okból nem tűnik be. Csak egy CPU-t látok 100%-on. 23.04.2021
  • Helyezze a Threads.@threads elemet a külső hurokra (i fölé), és adja hozzá a @inbounds elemet. Ez 20-szoros sebességet adott a számítógépemen, 100 ms alatt futva 200 000 x 10 000 képponton. 200000x30000 mátrix esetén 330 ms alatt futott le. 23.04.2021
  • BTW, a AU létrehozása most már eléggé megterheli a türelmemet, de tartomány helyett egy sorból kell mintát venni, és sokkal gyorsabb. Tehát rand(UInt8.((0, 1)), rows, cols). 23.04.2021
  • @DNF Már áthelyeztem a @Threads-t a külső ciklusba, és nulla volt a hatása. Hová tegyem a @inbounds makrót. 23.04.2021
  • Ez rendkívül furcsa. A @threads makrót mozgatva meg kell egyeznie a numbával. Például tegye a Threads.@threads-t az első ciklus elé, és a @inbounds-t a második elé. 23.04.2021

  • 2

    Az egyszálú BitArray-vé való konvertáláshoz a Bool.(AU) AU .% Bool (lásd a szerkesztési megjegyzést) hatékonynak kell lennie:

    using Random
    using BenchmarkTools
    AU = rand(Bool, 10_000, 200_000)
    @benchmark Bool.($AU)
    
    BenchmarkTools.Trial: 
      memory estimate:  238.42 MiB
      allocs estimate:  4
      --------------
      minimum time:     658.897 ms (0.00% GC)
      median time:      672.948 ms (0.00% GC)
      mean time:        676.287 ms (0.86% GC)
      maximum time:     710.870 ms (6.57% GC)
      --------------
      samples:          8
      evals/sample:     1
    

    Szerkesztés: Most jöttem rá, hogy az Bool.(AU) nem fog jól működni neked, mert 8 bites egész számok tömbjéből konvertálsz, nem Boolok tömbjéből, ezért a Bool.(AU) ellenőrizni kell, hogy a AU minden eleme 0 vagy 1. Ehelyett használja a AU .% Bool értéket, amely minden egész számból a legkevesebb bitet veszi fel, és a fent bemutatott teljesítményt nyújtja.

    23.04.2021
  • A Bool.(AU) valóban lassú. AU .% A Bool jól teljesít, de nem veri a compute2()-t a @views makróval. 23.04.2021
  • Új anyagok

    A rádiógomb ellenőrzött eseményének használata a jQueryben
    Ebben a cikkben látni fogjuk, hogyan kell dolgozni a jquery választógombbal ellenőrzött eseményeivel. A választógombok HTML gombok, amelyek segítenek kiválasztani egyetlen értéket egy csoportból...

    Körkörös függőségek megoldása terraformban adatforrásokkal – lépésről lépésre
    Mi az a körkörös függőségek Dolgozzunk egy egyszerű eseten, amikor az SQS-sor és az S3-vödör közötti körkörös függőség problémája van egy egymástól függő címkeérték miatt. provider..

    Miért érdemes elkezdeni a kódolást 2023-ban?
    01100011 01101111 01100100 01100101 — beep boop beep boop Világunk folyamatosan fejlődik a technológia körül, és naponta fejlesztenek új technológiákat a valós problémák megoldására. Amint..

    🎙 Random Noise #2  – Örökbefogadás és hit
    az analitika íratlan világának gondozása Szeretné, hogy ezek a frissítések a postaládájába kerüljenek? Iratkozzon fel itt . "Ha önvezető autókat gyártanak, akkor mi miért ne..

    A legrosszabb politika és prediktív modellek májátültetésre jelöltek számára az Egyesült Államokban
    A máj (vagy óangolul lifer) az emberi test legnehezebb belső szervére utal, amely csendesen működik a nap 24 órájában. Mit csinál a máj? 500 feladatot hajt végre a szervezet egészségének..

    5 webhely, amely 2022-ben fejleszti front-end fejlesztői készségeit
    Frontendmentor.io A tényleges projektek létrehozásával a Frontendmentor.io segítséget nyújt a front-end kódolási képességeinek fejlesztésében. A kódolást azután kezdheti meg, hogy..

    Mikor kell használni a Type-t az interfészhez képest a TypeScriptben?
    A TypeScript a JavaScript gépelt szuperkészlete, amely statikus gépelést ad a nyelvhez. Ez megkönnyíti a robusztus és karbantartható kód írását azáltal, hogy a hibákat a fordítási időben..