Izboljšajte zmogljivost vaše Python kode za več velikostnih redov. Ta celovit vodnik raziskuje SIMD, vektorizacijo, NumPy in napredne knjižnice za globalne razvijalce.
Odklepanje zmogljivosti: Celovit vodnik po SIMD in vektorizaciji v Pythonu
V svetu računalništva je hitrost najpomembnejša. Ne glede na to, ali ste podatkovni znanstvenik, ki uči model strojnega učenja, finančni analitik, ki izvaja simulacijo, ali programski inženir, ki obdeluje velike nabore podatkov, učinkovitost vaše kode neposredno vpliva na produktivnost in porabo virov. Python, ki je znan po svoji preprostosti in berljivosti, ima dobro znano Ahilovo peto: njegovo zmogljivost pri računsko intenzivnih nalogah, zlasti tistih, ki vključujejo zanke. Kaj pa, če bi lahko operacije izvajali na celotnih zbirkah podatkov hkrati, namesto na enem elementu naenkrat? To je obljuba vektoriziranega računanja, paradigme, ki jo poganja funkcija CPE, imenovana SIMD.
Ta vodnik vas bo popeljal v globine sveta operacij SIMD (Single Instruction, Multiple Data) in vektorizacije v Pythonu. Potovali bomo od temeljnih konceptov arhitekture CPE do praktične uporabe zmogljivih knjižnic, kot so NumPy, Numba in Cython. Naš cilj je, da vas, ne glede na vašo geografsko lokacijo ali ozadje, opremimo z znanjem za preoblikovanje vaše počasne, z zankami polne Python kode v visoko optimizirane, visoko zmogljive aplikacije.
Temelj: Razumevanje arhitekture CPE in SIMD
Da bi zares cenili moč vektorizacije, moramo najprej pogledati pod pokrov, kako deluje sodobna centralna procesna enota (CPE). Čarovnija SIMD ni programski trik; to je strojna zmožnost, ki je revolucionirala numerično računanje.
Od SISD do SIMD: Premik paradigme v računanju
Dolga leta je bil prevladujoč model računanja SISD (Single Instruction, Single Data). Predstavljajte si kuharja, ki natančno seklja eno zelenjavo naenkrat. Kuhar ima en ukaz ('sekljaj') in deluje na enem podatku (enem korenčku). To je analogno tradicionalnemu jedru CPE, ki izvaja en ukaz na enem podatku na cikel. Preprosta Python zanka, ki sešteva števila iz dveh seznamov enega za drugim, je odličen primer modela SISD:
# Konceptualna operacija SISD
result = []
for i in range(len(list_a)):
# En ukaz (seštevanje) na enem podatku (a[i], b[i]) naenkrat
result.append(list_a[i] + list_b[i])
Ta pristop je zaporedni in povzroča znatne dodatne stroške zaradi Pythonovega interpreterja pri vsaki iteraciji. Zdaj pa si predstavljajte, da temu kuharju daste specializiran stroj, ki lahko z enim potegom ročice hkrati naseklja celo vrsto štirih korenčkov. To je bistvo SIMD (Single Instruction, Multiple Data). CPE izda en sam ukaz, vendar ta deluje na več podatkovnih točkah, združenih v posebnem, širokem registru.
Kako SIMD deluje na sodobnih CPE
Sodobni CPE-ji proizvajalcev, kot sta Intel in AMD, so opremljeni s posebnimi registri SIMD in nabori ukazov za izvajanje teh vzporednih operacij. Ti registri so veliko širši od splošnonamenskih registrov in lahko hranijo več podatkovnih elementov hkrati.
- Registri SIMD: To so veliki strojni registri na CPE. Njihove velikosti so se s časom razvijale: 128-bitni, 256-bitni in zdaj so pogosti 512-bitni registri. 256-bitni register lahko na primer hrani osem 32-bitnih števil s plavajočo vejico ali štiri 64-bitna števila s plavajočo vejico.
- Nabori ukazov SIMD: CPE-ji imajo specifične ukaze za delo s temi registri. Morda ste že slišali za te kratice:
- SSE (Streaming SIMD Extensions): Starejši 128-bitni nabor ukazov.
- AVX (Advanced Vector Extensions): 256-bitni nabor ukazov, ki ponuja znatno povečanje zmogljivosti.
- AVX2: Razširitev AVX z več ukazi.
- AVX-512: Zmogljiv 512-bitni nabor ukazov, ki ga najdemo v mnogih sodobnih strežniških in visoko zmogljivih namiznih CPE-jih.
Poglejmo si to vizualno. Recimo, da želimo sešteti dve polji, `A = [1, 2, 3, 4]` in `B = [5, 6, 7, 8]`, kjer je vsako število 32-bitno celo število. Na CPE s 128-bitnimi registri SIMD:
- CPE naloži `[1, 2, 3, 4]` v register SIMD 1.
- CPE naloži `[5, 6, 7, 8]` v register SIMD 2.
- CPE izvede en sam vektoriziran ukaz 'seštej' (`_mm_add_epi32` je primer pravega ukaza).
- V enem samem taktu strojna oprema vzporedno izvede štiri ločena seštevanja: `1+5`, `2+6`, `3+7`, `4+8`.
- Rezultat, `[6, 8, 10, 12]`, se shrani v drug register SIMD.
To je 4-kratna pohitritev v primerjavi s pristopom SISD za jedro izračuna, ne da bi sploh upoštevali ogromno zmanjšanje stroškov pošiljanja ukazov in zank.
Razkorak v zmogljivosti: Skalarne proti vektorskim operacijam
Izraz za tradicionalno operacijo, ki se izvaja na enem elementu naenkrat, je skalarna operacija. Operacija na celotnem polju ali podatkovnem vektorju je vektorska operacija. Razlika v zmogljivosti ni majhna; lahko je več velikostnih redov.
- Zmanjšani dodatni stroški: V Pythonu vsaka iteracija zanke vključuje dodatne stroške: preverjanje pogoja zanke, povečevanje števca in pošiljanje operacije skozi interpreter. Ena sama vektorska operacija ima samo eno pošiljanje, ne glede na to, ali ima polje tisoč ali milijon elementov.
- Strojna vzporednost: Kot smo videli, SIMD neposredno izkorišča vzporedne procesne enote znotraj enega samega jedra CPE.
- Izboljšana lokalnost predpomnilnika: Vektorizirane operacije običajno berejo podatke iz sosednjih blokov pomnilnika. To je zelo učinkovito za sistem predpomnjenja CPE, ki je zasnovan za vnaprejšnje nalaganje podatkov v zaporednih kosih. Naključni vzorci dostopa v zankah lahko vodijo do pogostih 'zgreškov predpomnilnika' (cache misses), ki so neverjetno počasni.
Pythonski način: Vektorizacija z NumPy
Razumevanje strojne opreme je fascinantno, vendar vam za izkoriščanje njene moči ni treba pisati nizkonivojske zbirne kode. Ekosistem Pythona ima fenomenalno knjižnico, ki omogoča dostopno in intuitivno vektorizacijo: NumPy.
NumPy: Temelj znanstvenega računanja v Pythonu
NumPy je temeljni paket za numerično računanje v Pythonu. Njegova osrednja značilnost je zmogljiv N-dimenzionalni objekt polja, `ndarray`. Prava čarovnija NumPyja je v tem, da njegove najpomembnejše rutine (matematične operacije, manipulacija s polji itd.) niso napisane v Pythonu. So visoko optimizirana, vnaprej prevedena koda v C-ju ali Fortranu, ki je povezana z nizkonivojskimi knjižnicami, kot sta BLAS (Basic Linear Algebra Subprograms) in LAPACK (Linear Algebra Package). Te knjižnice so pogosto prilagojene s strani proizvajalcev za optimalno uporabo naborov ukazov SIMD, ki so na voljo na gostiteljskem CPE.
Ko v NumPy napišete `C = A + B`, ne izvajate zanke v Pythonu. Pošiljate en sam ukaz visoko optimizirani funkciji v C-ju, ki izvede seštevanje z uporabo ukazov SIMD.
Praktični primer: Od Pythonove zanke do NumPy polja
Poglejmo to v praksi. Sešteli bomo dve veliki polji števil, najprej s čisto Pythonovo zanko in nato z NumPy. To kodo lahko zaženete v Jupyter Notebooku ali Python skripti, da vidite rezultate na svojem računalniku.
Najprej pripravimo podatke:
import time
import numpy as np
# Uporabimo veliko število elementov
num_elements = 10_000_000
# Čisti Python seznami
list_a = [i * 0.5 for i in range(num_elements)]
list_b = [i * 0.2 for i in range(num_elements)]
# NumPy polja
array_a = np.arange(num_elements) * 0.5
array_b = np.arange(num_elements) * 0.2
Zdaj pa izmerimo čas izvajanja čiste Pythonove zanke:
start_time = time.time()
result_list = [0] * num_elements
for i in range(num_elements):
result_list[i] = list_a[i] + list_b[i]
end_time = time.time()
python_duration = end_time - start_time
print(f"Čista Python zanka je trajala: {python_duration:.6f} sekund")
In zdaj enakovredna operacija z NumPy:
start_time = time.time()
result_array = array_a + array_b
end_time = time.time()
numpy_duration = end_time - start_time
print(f"NumPy vektorizirana operacija je trajala: {numpy_duration:.6f} sekund")
# Izračunajmo pohitritev
if numpy_duration > 0:
print(f"NumPy je približno {python_duration / numpy_duration:.2f}x hitrejši.")
Na tipičnem sodobnem računalniku bo rezultat osupljiv. Pričakujete lahko, da bo NumPy različica od 50 do 200-krat hitrejša. To ni manjša optimizacija; to je temeljna sprememba v načinu izvajanja izračuna.
Univerzalne funkcije (ufuncs): Motor hitrosti NumPyja
Operacija, ki smo jo pravkar izvedli (`+`), je primer NumPy univerzalne funkcije ali ufunc. To so funkcije, ki delujejo na `ndarray`-jih na način element za elementom. So jedro vektorizirane moči NumPyja.
Primeri ufunc-ov vključujejo:
- Matematične operacije: `np.add`, `np.subtract`, `np.multiply`, `np.divide`, `np.power`.
- Trigonometrične funkcije: `np.sin`, `np.cos`, `np.tan`.
- Logične operacije: `np.logical_and`, `np.logical_or`, `np.greater`.
- Eksponentne in logaritemske funkcije: `np.exp`, `np.log`.
Te operacije lahko verižite skupaj za izražanje kompleksnih formul, ne da bi kdaj napisali eksplicitno zanko. Poglejmo izračun Gaussove funkcije:
# x je NumPy polje z milijonom točk
x = np.linspace(-5, 5, 1_000_000)
# Skalarni pristop (zelo počasi)
result = []
for val in x:
term = -0.5 * (val ** 2)
result.append((1 / np.sqrt(2 * np.pi)) * np.exp(term))
# Vektoriziran NumPy pristop (izjemno hitro)
result_vectorized = (1 / np.sqrt(2 * np.pi)) * np.exp(-0.5 * x**2)
Vektorizirana različica ni le dramatično hitrejša, ampak je tudi bolj jedrnata in berljiva za tiste, ki so seznanjeni z numeričnim računanjem.
Onkraj osnov: Razširjanje (Broadcasting) in razporeditev v pomnilniku
Zmogljivosti vektorizacije v NumPyju so dodatno izboljšane s konceptom, imenovanim razširjanje (broadcasting). Ta opisuje, kako NumPy obravnava polja z različnimi oblikami med aritmetičnimi operacijami. Razširjanje vam omogoča izvajanje operacij med velikim poljem in manjšim (npr. skalarjem), ne da bi eksplicitno ustvarjali kopije manjšega polja, da bi se ujemale z obliko večjega. To prihrani pomnilnik in izboljša zmogljivost.
Na primer, za pomnožitev vsakega elementa v polju s faktorjem 10 vam ni treba ustvariti polja, polnega desetic. Preprosto napišete:
my_array = np.array([1, 2, 3, 4])
scaled_array = my_array * 10 # Razširjanje skalarja 10 po my_array
Poleg tega je način, kako so podatki razporejeni v pomnilniku, ključnega pomena. NumPy polja so shranjena v sosednjem bloku pomnilnika. To je bistveno za SIMD, ki zahteva, da se podatki zaporedno nalagajo v njegove široke registre. Razumevanje razporeditve pomnilnika (npr. C-jev slog po vrsticah proti Fortranovemu slogu po stolpcih) postane pomembno za napredno uglaševanje zmogljivosti, zlasti pri delu z večdimenzionalnimi podatki.
Premikanje meja: Napredne knjižnice SIMD
NumPy je prvo in najpomembnejše orodje za vektorizacijo v Pythonu. Vendar, kaj se zgodi, ko vašega algoritma ni mogoče enostavno izraziti z uporabo standardnih NumPy ufuncs? Morda imate zanko s kompleksno pogojno logiko ali algoritem po meri, ki ni na voljo v nobeni knjižnici. Tu pridejo v poštev naprednejša orodja.
Numba: Prevajanje Just-In-Time (JIT) za hitrost
Numba je izjemna knjižnica, ki deluje kot prevajalnik Just-In-Time (JIT). Prebere vašo Python kodo in jo med izvajanjem prevede v visoko optimizirano strojno kodo, ne da bi vam bilo treba zapustiti okolje Pythona. Še posebej odlična je pri optimizaciji zank, ki so glavna šibkost standardnega Pythona.
Najpogostejši način uporabe Numbe je preko njenega dekoratorja, `@jit`. Vzemimo primer, ki ga je v NumPyju težko vektorizirati: simulacijska zanka po meri.
import numpy as np
from numba import jit
# Hipotetična funkcija, ki jo je težko vektorizirati v NumPyju
def simulate_particles_python(positions, velocities, steps):
for _ in range(steps):
for i in range(len(positions)):
# Nekaj kompleksne, od podatkov odvisne logike
if positions[i] > 0:
velocities[i] -= 9.8 * 0.01
else:
velocities[i] = -velocities[i] * 0.9 # Neelastičen trk
positions[i] += velocities[i] * 0.01
return positions
# Popolnoma enaka funkcija, vendar z Numba JIT dekoratorjem
@jit(nopython=True, fastmath=True)
def simulate_particles_numba(positions, velocities, steps):
for _ in range(steps):
for i in range(len(positions)):
if positions[i] > 0:
velocities[i] -= 9.8 * 0.01
else:
velocities[i] = -velocities[i] * 0.9
positions[i] += velocities[i] * 0.01
return positions
S preprostim dodajanjem dekoratorja `@jit(nopython=True)` Numbi poveste, naj to funkcijo prevede v strojno kodo. Argument `nopython=True` je ključen; zagotavlja, da Numba generira kodo, ki se ne zateka k počasnemu Pythonovemu interpreterju. Zastavica `fastmath=True` omogoča Numbi uporabo manj natančnih, a hitrejših matematičnih operacij, kar lahko omogoči samodejno vektorizacijo. Ko Numbin prevajalnik analizira notranjo zanko, bo pogosto sposoben samodejno generirati ukaze SIMD za obdelavo več delcev hkrati, tudi s pogojno logiko, kar vodi do zmogljivosti, ki se kosa ali celo presega ročno napisano kodo v C-ju.
Cython: Mešanje Pythona s C/C++
Preden je Numba postala priljubljena, je bil Cython glavno orodje za pospeševanje Python kode. Cython je nadmnožica jezika Python, ki podpira tudi klicanje funkcij C/C++ in deklariranje C tipov za spremenljivke in atribute razredov. Deluje kot prevajalnik pred časom (ahead-of-time - AOT). Svojo kodo napišete v datoteko `.pyx`, ki jo Cython prevede v izvorno datoteko C/C++, ta pa se nato prevede v standardni razširitveni modul za Python.
Glavna prednost Cythona je natančen nadzor, ki ga omogoča. Z dodajanjem statičnih deklaracij tipov lahko odstranite večino dinamičnih dodatnih stroškov Pythona.
Preprosta funkcija v Cythonu bi lahko izgledala takole:
# V datoteki z imenom 'sum_module.pyx'
def sum_typed(long[:] arr):
cdef long total = 0
cdef int i
for i in range(arr.shape[0]):
total += arr[i]
return total
Tukaj se `cdef` uporablja za deklariranje spremenljivk na nivoju C-ja (`total`, `i`), `long[:]` pa zagotavlja tipiziran pogled na pomnilnik vhodnega polja. To omogoča Cythonu, da generira visoko učinkovito zanko v C-ju. Za strokovnjake Cython celo ponuja mehanizme za neposredno klicanje SIMD vgrajenih funkcij (intrinsics), kar ponuja najvišjo raven nadzora za aplikacije, kjer je zmogljivost ključnega pomena.
Specializirane knjižnice: Pogled v ekosistem
Visoko zmogljiv ekosistem Pythona je obsežen. Poleg NumPyja, Numbe in Cythona obstajajo tudi druga specializirana orodja:
- NumExpr: Hiter ocenjevalec numeričnih izrazov, ki lahko včasih prehiti NumPy z optimizacijo porabe pomnilnika in uporabo več jeder za ocenjevanje izrazov, kot je `2*a + 3*b`.
- Pythran: Prevajalnik pred časom (AOT), ki prevede podmnožico Python kode, zlasti kodo, ki uporablja NumPy, v visoko optimiziran C++11, kar pogosto omogoča agresivno SIMD vektorizacijo.
- Taichi: Domensko specifičen jezik (DSL), vgrajen v Python za visoko zmogljivo vzporedno računanje, še posebej priljubljen v računalniški grafiki in fizikalnih simulacijah.
Praktični premisleki in najboljše prakse za globalno občinstvo
Pisanje visoko zmogljive kode vključuje več kot le uporabo prave knjižnice. Tukaj je nekaj univerzalno uporabnih najboljših praks.
Kako preveriti podporo za SIMD
Zmogljivost, ki jo dosežete, je odvisna od strojne opreme, na kateri se izvaja vaša koda. Pogosto je koristno vedeti, kateri nabori ukazov SIMD so podprti na določenem CPE. Uporabite lahko večplatformsko knjižnico, kot je `py-cpuinfo`.
# Namestite z: pip install py-cpuinfo
import cpuinfo
info = cpuinfo.get_cpu_info()
supported_flags = info.get('flags', [])
print("Podpora za SIMD:")
if 'avx512f' in supported_flags:
print("- AVX-512 podprt")
elif 'avx2' in supported_flags:
print("- AVX2 podprt")
elif 'avx' in supported_flags:
print("- AVX podprt")
elif 'sse4_2' in supported_flags:
print("- SSE4.2 podprt")
else:
print("- Osnovna podpora SSE ali starejša.")
To je ključnega pomena v globalnem kontekstu, saj se lahko instance v oblaku in strojna oprema uporabnikov med regijami močno razlikujejo. Poznavanje zmožnosti strojne opreme vam lahko pomaga razumeti značilnosti zmogljivosti ali celo prevesti kodo s specifičnimi optimizacijami.
Pomen podatkovnih tipov
Operacije SIMD so zelo specifične za podatkovne tipe (`dtype` v NumPyju). Širina vašega registra SIMD je fiksna. To pomeni, da če uporabite manjši podatkovni tip, lahko v en register spravite več elementov in obdelate več podatkov na ukaz.
Na primer, 256-bitni register AVX lahko hrani:
- Štiri 64-bitna števila s plavajočo vejico (`float64` ali `double`).
- Osem 32-bitnih števil s plavajočo vejico (`float32` ali `float`).
Če zahteve po natančnosti vaše aplikacije lahko zadostijo 32-bitni float-i, lahko s preprosto spremembo `dtype` vaših NumPy polj iz `np.float64` (privzeta vrednost na mnogih sistemih) v `np.float32` potencialno podvojite vašo računsko prepustnost na strojni opremi, ki podpira AVX. Vedno izberite najmanjši podatkovni tip, ki zagotavlja zadostno natančnost za vaš problem.
Kdaj NE vektorizirati
Vektorizacija ni čudežno zdravilo. Obstajajo scenariji, kjer je neučinkovita ali celo kontraproduktivna:
- Od podatkov odvisen kontrolni tok: Zanke s kompleksnimi `if-elif-else` vejami, ki so nepredvidljive in vodijo do razhajajočih se poti izvajanja, so za prevajalnike zelo težke za samodejno vektorizacijo.
- Zaporedne odvisnosti: Če je izračun za en element odvisen od rezultata prejšnjega elementa (npr. v nekaterih rekurzivnih formulah), je problem po naravi zaporedni in ga ni mogoče vzporediti s SIMD.
- Majhni nabori podatkov: Za zelo majhna polja (npr. manj kot ducat elementov) so lahko dodatni stroški priprave vektoriziranega klica funkcije v NumPyju večji od stroškov preproste, neposredne Python zanke.
- Nepravilen dostop do pomnilnika: Če vaš algoritem zahteva skakanje po pomnilniku na nepredvidljiv način, bo to porazilo mehanizme predpomnjenja in vnaprejšnjega nalaganja CPE-ja, kar izniči ključno prednost SIMD.
Študija primera: Obdelava slik s SIMD
Utrdimo te koncepte s praktičnim primerom: pretvorba barvne slike v sivine. Slika je le 3D polje števil (višina x širina x barvni kanali), zaradi česar je popoln kandidat za vektorizacijo.
Standardna formula za svetilnost je: `Sivina = 0.299 * R + 0.587 * G + 0.114 * B`.
Predpostavimo, da imamo sliko naloženo kot NumPy polje oblike `(1920, 1080, 3)` s podatkovnim tipom `uint8`.
Metoda 1: Čista Python zanka (Počasen način)
def to_grayscale_python(image):
h, w, _ = image.shape
grayscale_image = np.zeros((h, w), dtype=np.uint8)
for r in range(h):
for c in range(w):
pixel = image[r, c]
gray_value = 0.299 * pixel[0] + 0.587 * pixel[1] + 0.114 * pixel[2]
grayscale_image[r, c] = int(gray_value)
return grayscale_image
To vključuje tri gnezdeni zanki in bo neverjetno počasno za sliko visoke ločljivosti.
Metoda 2: NumPy vektorizacija (Hiter način)
def to_grayscale_numpy(image):
# Določimo uteži za R, G, B kanale
weights = np.array([0.299, 0.587, 0.114])
# Uporabimo skalarni produkt vzdolž zadnje osi (barvni kanali)
grayscale_image = np.dot(image[...,:3], weights).astype(np.uint8)
return grayscale_image
V tej različici izvedemo skalarni produkt. NumPyjev `np.dot` je visoko optimiziran in bo uporabil SIMD za množenje in seštevanje vrednosti R, G, B za veliko slikovnih pik hkrati. Razlika v zmogljivosti bo kot noč in dan – zlahka 100-kratna pohitritev ali več.
Prihodnost: SIMD in razvijajoča se pokrajina Pythona
Svet visoko zmogljivega Pythona se nenehno razvija. Zloglasni Global Interpreter Lock (GIL), ki preprečuje več nitim izvajanje Pythonove bajtne kode vzporedno, je postavljen pod vprašaj. Projekti, ki si prizadevajo narediti GIL neobvezen, bi lahko odprli nove poti za vzporednost. Vendar pa SIMD deluje na ravni pod-jedra in GIL nanj ne vpliva, zaradi česar je zanesljiva in za prihodnost varna strategija optimizacije.
Ker strojna oprema postaja vse bolj raznolika, s specializiranimi pospeševalniki in zmogljivejšimi vektorskimi enotami, bodo orodja, ki abstrahirajo podrobnosti strojne opreme, hkrati pa zagotavljajo zmogljivost – kot sta NumPy in Numba – postala še bolj ključna. Naslednji korak od SIMD znotraj CPE je pogosto SIMT (Single Instruction, Multiple Threads) na GPE, in knjižnice, kot je CuPy (neposredna zamenjava za NumPy na GPE-jih NVIDIA), uporabljajo enake principe vektorizacije v še večjem obsegu.
Zaključek: Sprejmite vektor
Potovali smo od jedra CPE do visokonivojskih abstrakcij Pythona. Ključni zaključek je, da morate za pisanje hitre numerične kode v Pythonu razmišljati v poljih, ne v zankah. To je bistvo vektorizacije.
Povzemimo našo pot:
- Problem: Čiste Python zanke so počasne za numerične naloge zaradi dodatnih stroškov interpreterja.
- Strojna rešitev: SIMD omogoča enemu samemu jedru CPE, da istočasno izvede isto operacijo на več podatkovnih točkah.
- Glavno orodje v Pythonu: NumPy je temeljni kamen vektorizacije, ki zagotavlja intuitiven objekt polja in bogato knjižnico ufuncs, ki se izvajajo kot optimizirana koda v C/Fortranu, ki podpira SIMD.
- Napredna orodja: Za algoritme po meri, ki jih ni enostavno izraziti v NumPyju, Numba ponuja prevajanje JIT za samodejno optimizacijo vaših zank, medtem ko Cython ponuja natančen nadzor z mešanjem Pythona in C-ja.
- Način razmišljanja: Učinkovita optimizacija zahteva razumevanje podatkovnih tipov, pomnilniških vzorcev in izbiro pravega orodja za delo.
Naslednjič, ko se boste znašli pri pisanju zanke `for` za obdelavo velikega seznama števil, se ustavite in vprašajte: "Ali lahko to izrazim kot vektorsko operacijo?" S sprejetjem tega vektoriziranega načina razmišljanja lahko odklenete pravo zmogljivost sodobne strojne opreme in povzdignete svoje Python aplikacije na novo raven hitrosti in učinkovitosti, ne glede na to, kje na svetu programirate.