Ubuntu Suomen keskustelualueet
Muut alueet => Yleistä keskustelua => Aiheen aloitti: ilkant - 21.01.21 - klo:18.30
-
Teinpä pienen python-ohjelman ratkaistakseni matemaattisen yhtälön: 4**x + 6**x = 9**x.
for x in range(1,100000):
if 4**x + 6**x == 9**x:
print(x)
Kun katselin Kubuntun KSysQuardilla järjestelmän kuormitusta, havaitsin HP 8200 koneella ytimen 2 kuormituksen olevan lähes koko ajan 100 % ja muut ytimet 1,3 ja 4 olivat ihan muutama prosentti hetkellisesti. Joskus esim. ydin 4 oli 20 % kuormalla pienen hetken.
Ihmettelen tätä kun olen vuosia sitten kuullut, että Linux tasaa hyvin kuormaa kahdelle ytimelle. Ainakin paremmin kuin Windows.
-
Ihmettelen tätä kun olen vuosia sitten kuullut, että Linux tasaa hyvin kuormaa kahdelle ytimelle. Ainakin paremmin kuin Windows.
Se edellyttää, että kuorma on säikeistetty tai jaettu useaan prosessiin. Python-skriptisi suoritetaan yhdessä säikeessä. Joudut säikeistämään koodin itse, jos haluat, että sitä suoritetaan rinnakkain.
Linux ei myöskään helposti siirrä kuormaa ytimeltä toiselle, toisin kuin Windowsilla oli ainakin joskus aikoinaan tapana tehdä (https://www.reddit.com/r/linuxmasterrace/comments/60e2u8/the_inferiority_of_windows_cpu_scheduler_vs/). Sellainen pallottelu heikentää suorituskykyä ja todennäköisesti haittaa prosessorin virransäästötoimintoja.
Tässä pari artikkelia aiheesta:
https://medium.com/mindful-engineering/multithreading-multiprocessing-in-python3-f6314ab5e23f
https://towardsdatascience.com/modern-parallel-and-distributed-python-a-quick-tutorial-on-ray-99f8d70369b8
-
Ajoin tuon ohjelman Kubuntussa terminaaliruudussa.
python3 yhtalo.py
Jos tällä nyt on mitään merkitystä tuossa. Jäi mainitsematta alkuperäisessä viestissä. Varmaan tilanne on sama, jos tuon ohjelman koodaisi esim. pyCharmilla (https://www.jetbrains.com/pycharm/) ja ajaisi siinä. Ja sama juttu Javalla toteutettuna.
Ja kiitos nosita linkeistä tietolähteisiin!
-
Jos tällä nyt on mitään merkitystä tuossa. Jäi mainitsematta alkuperäisessä viestissä. Varmaan tilanne on sama, jos tuon ohjelman koodaisi esim. pyCharmilla (https://www.jetbrains.com/pycharm/) ja ajaisi siinä. Ja sama juttu Javalla toteutettuna.
Joo, ei riipu ohjelmointiympäristöstä, eikä juuri ohjelmointikielestäkään, ellei siinä ole silmukoiden automaattista rinnakkaistusta. Joillain kielillä tämä on helpompaa kuin toisilla, mutta viime kädessä koodaaja on itse vastuussa ongelman jakamisesta rinnakkaisesti suoritettaviin osiin.
-
Teinpä pienen python-ohjelman ratkaistakseni matemaattisen yhtälön: 4**x + 6**x = 9**x.
Ööh, minäkin kiinnostuin ja muokkasin koodia seuraavasti:
#! /usr/bin/python3
for x in range(1,100000):
if (x % 100) == 0:
print ('x =', x)
if 4**x + 6**x == 9**x:
print ('***** Result =', x)
break
Mutta. Ei se löytänyt ratkaisua ainakaan välilta 1 - 99999. Entä sinulla?
-

Yhtälölle ei ole kokonaislukuratkaisua. x≈1,18681
https://www.wolframalpha.com/input/?i=solve+4**x+%2B+6**x+%3D+9**x (https://www.wolframalpha.com/input/?i=solve+4**x+%2B+6**x+%3D+9**x)
-
Asiaan liittyen tässä esimerkkejä miten kuormaa Java:lla jaetaan monelle ytimelle:
https://mkyong.com/java8/java-8-parallel-streams-examples/
Varmaan pythonille vastaavat systeemit?
-
Asiaan liittyen tässä esimerkkejä miten kuormaa Java:lla jaetaan monelle ytimelle:
https://mkyong.com/java8/java-8-parallel-streams-examples/
Varmaan pythonille vastaavat systeemit?
multiprosessing (https://docs.python.org/3/library/multiprocessing.html) moduulilla ja Pool (https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.Pool)-luokalla onnistuu. Suurin piirtein saman verran joutuu ylimääräistä minimissään koodaamaan.
Jos kyse olisi C-kielestä, niin alkuperäisen viestin esimerkin rinnakkaistaisin openmp (https://www.openmp.org/):llä. Sillä pääsisi verrattain vähällä lisäkoodilla.
Muokkaus: Lisätty linkit
-
Keskustelu hypersäikeistyksestä on nyt omassa aiheesaan: https://forum.ubuntu-fi.org/index.php?topic=56067.0
-
Noita ohjelmointiesimerkkejä kaipailinkin. Sekä javalle, että pythonille. Kiitos.
-
Tämä ei Kubuntun KsysGuardin mukaan jakanut tasaisesti kuormaa. Yhdellä ytimellä neljästä oli lähes 100 % kuormaa ja toisella silloin tällöin noin 20 % ja loput kaksi olivat lähes toimettomia. Ohjelma ajettu konsoli-ikkunassa.
import multiprocessing as mp
def foo(q):
for x in range(1,100000):
if (x % 100) == 0:
print ('x =', x)
if 4**x + 6**x == 9**x:
print ('***** Result =', x)
break
if __name__ == '__main__':
mp.set_start_method('spawn')
q = mp.Queue()
p = mp.Process(target=foo, args=(q,))
p.start()
print(q.get())
p.join()
-
Tässä toisessa kokeessa alkoi näkyä jo hiukan kuorman tasaamista. Edelleenkin se keksittyy yhdelle ytimelle.
(https://munkuvat.net/k/2021/02/03/baa68ad820e1586b1686ca26b8f6e7c7.png)
from multiprocessing import Process, Value, Array
def f(n, a):
n.value = 3.1415927
for i in range(len(a)):
a[i] = -a[i]
if __name__ == '__main__':
num = Value('d', 0.0)
arr = Array('i', range(1000000))
p = Process(target=f, args=(num, arr))
p.start()
p.join()
print(num.value)
print(arr[:])
-
Tällä ohjelmalla tasaus onnistui.
(https://munkuvat.net/k/2021/02/03/83ad6f02f56f40254301bdfbb0ef277c.jpg)
from multiprocessing import Pool, TimeoutError
import time
import os
def f(x):
return x*x
if __name__ == '__main__':
# start 4 worker processes
with Pool(processes=4) as pool:
# print "[0, 1, 4,..., 81]"
print(pool.map(f, range(10)))
# print same numbers in arbitrary order
for i in pool.imap_unordered(f, range(1000000)):
print(i)
# evaluate "f(20)" asynchronously
res = pool.apply_async(f, (20,)) # runs in *only* one process
print(res.get(timeout=1)) # prints "400"
# evaluate "os.getpid()" asynchronously
res = pool.apply_async(os.getpid, ()) # runs in *only* one process
print(res.get(timeout=1)) # prints the PID of that process
# launching multiple evaluations asynchronously *may* use more processes
multiple_results = [pool.apply_async(os.getpid, ()) for i in range(4)]
print([res.get(timeout=1) for res in multiple_results])
# make a single worker sleep for 10 secs
res = pool.apply_async(time.sleep, (10,))
try:
print(res.get(timeout=1))
except TimeoutError:
print("We lacked patience and got a multiprocessing.TimeoutError")
print("For the moment, the pool remains available for more work")
# exiting the 'with'-block has stopped the pool
print("Now the pool is closed and no longer available")
-
Niin tuo ensimmäinen ohjelma alustaa taulukon, käynnistää yhden rinnakkaisen prosessin, mutta jää odottelemaan sen loppumista. Käynnistetty prosessi muuttaa taulukon arvot negatiiviksi ja lopettaa itsensä, jolloin vuorostaan pääohjelman suoritus jatkuu. Lopuksi pääprosessi listaa taulukon sisällön stdoutiin.
Tuossa tapauksessa ei siis tehdä mitään rinnakkain, ja lisäksi käynnistetyn prosessin laskenta kestää vain 1 – 2 sekuntia, kun taas tuloksen listaaminen päätteeseen kestää päätteestä riippuen 3 – 30 sekuntia.
-
Joo, en lukenut tuon sivun tekstejä loppuun asti, ennen kuin kokeilin jo niitä koodeja. Siitä se johtui. Siellä teksteissä oli jotain muistialueen yhteiskäytöstä. Tuo viimeinen esimerkki tasasikin sitten kuorman.
Muistelen, että Linuxissa pystyy jollain parametrilla säätelemään prosessorin/prosessorien kokonaiskuormaa tai nice-arvoja. Tai ainakin isoissa Unix-koneissa voi. Nyt kun tuon tasaamisen jälkeen jää vielä 20 % tehoista käyttämättä, niin sen käyttöönotolla saisi ikään kuin viidennen ytimen työsuorituksen. Jos siis on tiedossa, että koneelle ei tule muualta lisää kuormaa ja koneenkäyttäjä päättää tälle tehtävälle antaa koneen kaikki resurssit.
-
Muistelen, että Linuxissa pystyy jollain parametrilla säätelemään prosessorin/prosessorien kokonaiskuormaa tai nice-arvoja.
Pystyy, mutta nykyisin prosessien skedulointi on varsin fiksua ja ytimiä on prosessoreissa paljon, joten manuaalista virittelyä ei yleensä tarvitse harrastaa. Lähinnä se voi olla tarpeen ajettaessa raskaita kuormia rinnakkain, jolloin nice-arvolla on mahdollista säätää niiden saamaa keskinäistä CPU-aikaa.
Nyt kun tuon tasaamisen jälkeen jää vielä 20 % tehoista käyttämättä, niin sen käyttöönotolla saisi ikään kuin viidennen ytimen työsuorituksen.
Havaitsemasi idle-osuus ei johdu siitä, etteikö kerneli haluaisi antaa kaikkea CPU-aikaa python-prosessiesi käyttöön. Veikkaan, että ongelmana on prosessien välisen kommunikoinnin viive. Esimerkkiohjelmasi suorittaa erittäin suuren määrän hyvin lyhytkestoisia laskentafunktioita neljän rinnakkaisen prosessin muodostamassa laskentapoolissa. Funktioiden käynnistäminen ja tulosten kerääminen vie valtaosan ajasta ja kuormittaa eniten pääprosessia. Prosessien välinen kommunikointi ja synkronointi aiheuttaa aina pienen viiveen tai odotuksen, vaikka käytettäisiin yhteistä muistia, ja tässä esimerkissä odotus haukkaa merkittävän osan suoritusajasta.
Kannattaa mieluummin jakaa laskenta pienempään määrään (10 - 1000 kpl) pitkäkestoisia funktioita, jotka sitten suoritetaan rinnakkain esimerkiksi tuon multiprocessing.Poolin avulla.
-
Muistan lukeneeni 1990-luvulla supertietokoneen ohjelmoinnista Fortran-kielellä. Siinä oli esimerkkejä, miten koodissa pienillä muutoksilla sai aikaan suuren eron lopputulosten ajoaikoihin. Nämä erot olivat mm. silmukoiden ehtolauseissa ja sen sellaisissa. Ehkä python-ohjelmassa on jotain samantapaista ja lähes kaikissa muissakin ohjelmointikielissä. Joissakin ohjelmointiympäristöissä on profiler, jolla voi mittailla ohjelmansa suoritusaikoja.
-
Kokeilin nyt 3.1 MHz Intelin i3-prosessorilla (4 ydintä) 8 GB muistin koneella (käyttis Kubuntu 20.10) Python-ohjelmankehitysympäristöllä pyCharm tekemään koodaamastani "pienen" python-ohjelman UML-kaavio. Kaavion teko kestää ja kestää ja levy sahaa. Mutta kuorma jakautuu hyvin eri ytimille.
Tulee vielä sellainen kysymys, että miksi kone varaa vain noin 4,8 GB 8 GB muistista? Jos se varaisin esim. 7 GB, niin homma voisi nopeutua?
(https://munkuvat.net/k/2021/04/01/ad0a62b1e4df1dc1fdce27420993eea2.png)
-
Tässä on vielä esimerkki, miten kuorma ei jakaudu aina tasaisesti ytimille. Asensin pyCharmiin Julia-pluginin ja ajoin seuraavan Julia-ohjelman totient-laskun (https://en.wikipedia.org/wiki/Euler%27s_totient_function).
import Pkg; Pkg.add("Primes")
using Primes
for k = 1:10000000 Primes.totient(k) end
Ja kuorma jakautui aina vaihdellen yhdelle prosessorille 100 % kuormalla. Kehitysympäristö on sama, mutta kielenä Pythonin sijaan Julia (jonka pitäisi olla nopea).
(https://munkuvat.net/k/2021/04/02/9823cfaf914240aed3d02b9f6b1991bc.png)
-
Kokeilin nyt 3.1 MHz Intelin i3-prosessorilla (4 ydintä) 8 GB muistin koneella (käyttis Kubuntu 20.10) Python-ohjelmankehitysympäristöllä pyCharm tekemään koodaamastani "pienen" python-ohjelman UML-kaavio. Kaavion teko kestää ja kestää ja levy sahaa. Mutta kuorma jakautuu hyvin eri ytimille.
Kokonaiskuorma on tuossakin silmämääräisesti katsottuna melko matala. Jokaisella ajanhetkellä vain yhden säikeen kuorma on korkeahko ja muut matalia. Kuorma näyttää siis koostuvan lyhytkestoisista säikeistämättömistä suorituksista, jotka päätyvät satunnaisesti eri ytimille. Ei siis kovin hyvä esimerkki rinnakkaisesta suorituksesta.
Tulee vielä sellainen kysymys, että miksi kone varaa vain noin 4,8 GB 8 GB muistista? Jos se varaisin esim. 7 GB, niin homma voisi nopeutua?
Kukin ohjelma varaa juuri verran muistia kuin tarvitsee. Se riippuu ohjelman toteutuksesta sekä mm. JVM:n tapauksessa myös asetuksista, joilla virtuaalikoneelle annetaan muistia. Jotkut sovellukset osaavat käyttää muistia vähemmän tai enemmän eri tilanteissa, riippuen vapaan muistin määrästä, mutta usein ylimääräisestä muistista ei olisi mitään hyötyä sovelluksen toiminnan kannalta.
Vapaaksi jäävä muisti ei kuitenkaan ole lainkaan turhaa, vaan Linux hyödyntää sitä levyvälimuistina, mikä nopeuttaa tiedostojen kirjoitamista sekä toistuvaa lukemista.
Ja kuorma jakautui aina vaihdellen yhdelle prosessorille 100 % kuormalla. Kehitysympäristö on sama, mutta kielenä Pythonin sijaan Julia (jonka pitäisi olla nopea).
Primes.totient-funktio ei ole itsessään säikeistetty, kuten ei myöskään for-silmukka. Säikeistäminen on sinun vastuullasi:
https://docs.julialang.org/en/v1/manual/multi-threading/
https://docs.julialang.org/en/v1/base/multi-threading/
Threads.@threads for k = 1:10000000 Primes.totient(k) end
Määritä säikeiden lukumäärä komentorivillä tai ympäristömuuttujalla, kun käynnistät skriptin:
julia --threads 4 totient.jl