Udviklere vælger for nemme løsninger: Ydelse kan øges 60.000 gange ved at omskrive kode

15. juni 2020 kl. 05:0243
Udviklere vælger for nemme løsninger: Ydelse kan øges 60.000 gange ved at omskrive kode
Illustration: Interxion.
Miniaturisering af halvlederkomponenter har skabt stadigt kraftigere processorer, som har gjort det muligt for programmører at prioritere hurtig skrivning af kode - i stedet for at gøre selve koden hurtig. Forskere mener ikke, at den udvikling kan fortsætte.
Artiklen er ældre end 30 dage
Manglende links i teksten kan sandsynligvis findes i bunden af artiklen.

Miniaturisering af halvlederkomponenter har været den vigtigste årsag til væksten i computerens ydeevne i mere end 50 år. Men når vi begynder at nærme os grænserne for, hvor meget mere det er muligt at krympe komponenterne, bliver man nødt til at ty til andre metoder for at øge ydelsen. Det skriver forskere ved Massachusetts Institute of Technologys (MIT) laboratorium for datalogi og kunstig intelligens (CSAIL) i tidsskriftet Science.

Log ind og læs videre
Du kan læse indholdet og deltage i debatten ved at logge ind eller oprette dig som ny bruger, helt gratis.
43 kommentarer.  Hop til debatten
Debatten
Log ind eller opret en bruger for at deltage i debatten.
settingsDebatindstillinger
43
17. juni 2020 kl. 20:16

Off Topic: I 1980 var dat0 på DIKU væsentligst et PASCAL-kursus, selvom man sagde noget andet. Man skrev sin kode på hulkort og kørte dem igennem et batch-system, hvorefter man ventede på udskriften. Senere kom der Picoliner med hjemmelavet skal (IDE). Jeg lærte BASIC ved at rette programmørfejl (div by zero) i digitaliseringsprogrammer til oliejagt. Så blev jeg sat til at digitalisere et Danmarkskort (kystlinier, indsøer og kommunegrænser) i 1:600.000 med et digibord fra Danbridge og en CommStore II. Kom igang da de fandt manualen til den. (først havde man prøvet et LINC-system uden manual). Det edte med at jeg udviklede en GIS-pilot (ZETA System) på VAX/VMS i FORTRAN-77 med GPGS (fra Oslo Univeritet) og DBMS V1.0. DGU kørte videre med ZETA i 11 år, før man fandt ESRI's ArcView (og Arc/INFO), hvor man også havde boks-søgning og (linje-) punkter i en særlig klasse, man havde jeg været igennem Digitals PC-hotline og area-supportcenter i Frankrig. Siden jeg kom tilbage derfra (i 1993, hvor dansker gik i koma over mit CV) var det kun DTU, der gav mig løn - i 7 måneder, inden jeg tog frivillig forsknings-orlov, for finde "nem og effektiv" programmering til mine (master- og ph.d-) studerende. Efter at have afsøgt mulighederne i compiler-switches [tog et par uger] fandt jeg "løkkesegmentering" med variabel bundtstørrelse i resten af november 1995. Statisk optimering virker ikke, hvis man skifter HW, men det gør variabel bundtstørrelse - men hvordan og hvorfor? Jeg turde ikke stå ved den formel, jeg havde fundet (som virkede):

NyKøretid = GkKøretid/(bundststørrelse^2) [forenklet her]

Professoren ville ikke høre om det, så min søn og måtte leve af min DEC-pension, indtil han fik sin studentereksamen. Han forsøgte også at få et forskud retur, men jeg havde altid faktureret 3 måneder bagud - så den gik ikke. [Morale: tag aldrig ulønnet forskningsorlov.]

Da KK i 2004, bevilgede mig FT-pension, begyndte jeg forfra på DTU (software teknolgi), det gik nogenlunde indtil 2012, hvor jeg blev lokket med på DTU-DOSSEE i Østrig, hvor jeg delte værelse med tre (3) andre studerende på et værelse, hvor 2 grand lits var erstattet af 4 enkeltsenge med godt 40 cm imellem, så de 3 andre smed mig ud. Da der ikke var andre ledige værelser i hele byen, måtte jeg tage for tidligt hjem. Passede dog min TA-tjans med videregående programmering indtil (og med) Fall 2014. Den slags er ikke så godt for blodtrykket, i 2013 fandt Øjenklinikken i Glostrup en grenvene-occlusion i højre øje, som blev behandlet med væksthæmmer indsprøjtet i øjet og et par år senere en blodudtrængning, som de gav laserbehandling (lysnåle i øjet). Indimellem vågnede en lørdag morgen (16/8-2014), gik lidt rundt, spiste nogle rysteribs, men tabte et ribs på gulvet og opdagede, da jeg ville samle det op, at det var blod - jeg hostede blod op (troede jeg), ringede 112 og bad om en ambulance - uden udrykning, klokken var 07. Lægen mente, at jeg lød for klar til at holde til 5 min i telefonen, men gik modvilligt med til sende en . Så sad jeg på trappen med toiletrulle og frysepose og hostede blod. Da ambulance endelig kom (efter 1 kvarter) - med udrykning og larm, gik jeg den imøde - for stoppe larmen; de målte BT til 241/139 og så gik det ba-bu til skadestuen på Bispebjerg Hospital. Der stod man klar til, hvad der skulle vise sig at være min luksusweekend: to gange i narkose (kikkertundersøgelser af mavesæk og lunger) og en nat på intensiv, hvor jeg kunne følge med i de 7 andre patienters kriser på en skærm. Fik også besøg af en yngre hjertelæge, som en laptop undersøgte mit hjerte med ultralyd og ordinerede op dosis blodtryksænkende medicin. Mandag vågnede jeg op på lungemedicinsk afd og blev tilbudt lidt suppe og et par stykker med roastbeef og ost (maden var ikke nogen at skrive hjem om, men efter 2 dages faste - begynde med at de havde pumpet fredagaftens flanksteak op gennem en plasticslanger gennem næsen, var det som ...). Overlægen imsisterede på at hvis (når) BT var under 130/90, kunne de lukke mig ud igen. Det tog er par forsøg, med ud kom jeg og kunne gå tilbage til Hulgårdsplads, hvor vi (min søn og jeg) tog 5A op ad bakken mod Brønshøj. Sammen med overlæge, der fulgte op på BBH fandt jeg i november 2015 ud af at det (ophostet blod) måtte være kommet fra en rift i næsen, da en øre-næse-hals-læge trukket en optisk fiberslange lidt hurtigt ud om mandagen.I slutningen af 2016 mente min søn, at jeg burde søge læge igen pga gang problemer og i juni 2017 var der endelig en tid. Da min nye læge hørte, at jeg var førtidspensionist, blev han rasende over jeg ikke kunne huske den side kollegaen fra Frederiksberg Hospital havde brugt en time i Tingbjerg (hvor KK havde kontor) på at opfinde til ære for kommunens overbebyrdede ansatte. (Jeg troede det var "kompensation" for at min søn og jeg tilsammen 1½ SU i 1997, da vi begyndte at læse på KU, han på DIKU og jeg på GEO [Østervoldgade 10, ja, tidligere DTH;-)]) Han (min nye læge) lovede mig rektal kikkert undersøgelse, tog nogle blodprøver og sendte mig ugen efter til ambulant "udredning" på Bispebjerg. Jeg blev ringet op dagen efter og tilsagt til næste dag. Hvor både sygeplejerske og neurolog undskyldte sig med at deres system kørte langsomt (SP), de troede at det var druk - men naturvidenskab og musik passede dårligt til alkohol. som er bedre til humanistiske fag (fx udviklingspsykologi, hvor jeg var forbi en privat studiegruppe før DIKU/DGU/DEC), med pga af musikken var jeg også dengang langt under 7 genstande om ugen. Gastrologen fandt ud af, at jeg ganske rigtigt var forstoppet; efter at andre organer var frikendt (over et par måneder) turde jeg endelig lade automatikken trykke to kæmpelorte (sælunger) ud. Når der begynder at går uger og måneder mellem de ambulante undersøgelser er man nok røget så langt ned ad listen, fordi de ikke fandt noget alvorligt - eller tror man dør inden;-). I juni 2018 måtte neurologen strække våben og henvise mig tilbage til det lokale lægehus, hvor jeg efter et par gange med en anden mandlig læge endte med at søge den kvindlige læge. Hun fandt efter mine klager over meget lavt BT (under 95/55) ud af at jeg kunne tage en måneds pause med blodtrykssænkende medicin (som har varet næsten 21 måneder). Jeg er de sidste 3 år inden FP, men har fundet ud af, at min økonomi nok holder til det sidste indtægtsfald.

Jeg lærte det meste grundlæggende af Digital, det kostede 10½ mio kr over 9½ år, fordi der var mange andre, som også skulle lønnes af mit arbejde - f.eks. biblotekaren, rejser og mine impressarioer (chefer), de to-tre PC'er + manualer jeg havde hjemme hele perioden; samt det faktum VMS-folkene smed alt hvad der sad en PC på (når den ikke var konsolsystem på en VAX) over skillevæggen. Jeg vandt en kundebenchmark på at simulere svartider (med netsimulator v1) i et banksystem til ca 2 mia FF. Min lab-opstilling kørte 4 gange for hurtigt (ST under ½ sek, i stedet for mindre end 2 sek) med dummy load på severen, så vi fandt ud af at belaste RDB-serveren med robot-klienter, som lavede rigtige transaktioner, i sidste øjeblik (vi (2 mgr + 2 prog + mig)måtte arbejde en uge i døgndrift for at blive klar med "robotterne" til kundens benchmark, men så sad min forudsigelse (+/- 10 %) også lige i skabet - skulle det vise sig. Alle var glade og jeg blev udlånt til det 200 M/K store team, som skulle implementere systemet. Efter et par måneder måtte jeg dog melde fra fordi der ikke var fremgang - det tog et halvt mere, før kunden opdagede det. Så var min impressario klar med næste projekt: performance af VAX/VMS, som PC server, der blev afholdt workshop med deltagere fra hele geografien - i samme uge, som Pathworks-mødet. Drøj tur at deltage i to samtidige workshops - heldigvis var de i samme bygning.

On topic: Og nu ser jeg så, at dansk IT har gjort det igen (anden tråd på V2). Ejendomvurderingssystem og domstolssystemerne, ting var godt nok lettere den man sad på et frikald nummer ;-)=== (med langt hvidt skæg) re: SP i 2017. InterSystems skriver klart og tydelig i deres doc, at det stiller (kald mig bare idiot igen) krav til datastrukturen at opnå god performance. På min ZETA-pilot (1982-83) måtte jeg bruge 3 måneder på at omskrive koden mellen RC1 (den rå domænemodel) og RC2 (med punkter i blokke, som var lettere (1 record for hvert linjesegment i stedet en for per punkt) for DBMS. RC2 var 60 gange hurtigere end RC1. I 1995 var løkkesegmentering 100 gange hurtige i C og 60 gange hurtige i Arcviews bytekodescript (java). re: NUMA, det er pointen i Intel multicore CPUer: L1I+L1D+ L2 er distribueret, mens L3 og main memory er shared.

Nu fik i så ikke historierne om DEC 433MP eller dengang jeg drev prof . Yale N Patt til nær vanvid på hansHPC-seminar i Cannes eller for den sags skyld hvordan jeg endelig fik prøvet Test Drevet Development på indledende programmering i 2004. Den er ellers sjov: måtte vende tilbage til mit første udkast og gøre det færdigt - med 2 døgn til deadline og sidste rettelse 10 min før DL. Det er både hurtigere og sjovere at skrive testen før koden. Der skal også være stof til næste rant.

42
17. juni 2020 kl. 11:48

Reflection mindsker abstraktion, da det giver adgang til intime detaljer om datarepræsentation osv. Reflection er noget skidt, som primært bruges fordi, sproget mangler faciliteter til generisk programmering.

Selvfølgeligt kan reflection bruges galt. Men brugt fornuftigt kan det hæve abstraktionsniveauet uden at man er nødt til at skifte til et andet sprog. Hvis vi specifikt snakker serialisering, så er en løsning hvor man tagger public classes og fields med en serialiseringsattribut ganske fornuftigt. Der hvor kæden falder af er når et library går ind og piller med private fields etc. Jeg skal ikke kunne sige om der findes esoteriske sprog hvor det laves mere overskueligt og elegant. I øvrigt er faciliteterne i .NET til generisk programmering ganske fornuftige om end der selvfølgeligt findes bedre løsninger.

Men tag ikke fejl: vi altid fundet bagveje når vi ikke kunne få adgang til data eller funktioner vi havde brug for. I de gamle MSDOS dage var der et utal af udokumenterede funktioner og memory adresser. Det samme findes andre steder. Ikke kønt men hvis målet er software der kører og man i øvrigt får pakket det pænt ind, så har jeg ikke noget problem med det hvis man kan leve med at der ikke er nogen garantier for fremtidig kompatibilitet.

39
17. juni 2020 kl. 09:26

Det beskrevne giver sig ofte udtryk i kvadradisk opførsel - det fungerer fint i mindre skala, men ikke på større. Et eksempel er serialisering under .NET (hvis man ikke forhindrer det, bliver der under overfladen, med et udenbys ord, brugt reflection).

Man kan sagtens få reflection til at fungere hurtigt til serialization på .NET. Der er intet galt med reflection og det kan hjælpe til at hæve abstraktionsniveauet i koden. Det handler om at man kun skal lave reflection en gang og så gemme resultatet. Der hvor problemet opstår er når man tilgår data fordi man ender med at boxe en masse value types (og dermed generere meget garbage) og det er rigtigt dyrt performance wise. Dette kan man også komme udenom ... men det er en længere historie.

Der findes et navn for det på udenbys: Shlemiel the Painter’s Algorithm

Som er det samme som algoritmic complexity (aka computational complexity aka store-O notation). Hvilket har været et emne på første år i computer science på DTU og DIKU siden engang i 80'erne (og måske endnu længere tilbage). Desværre oplever jeg at mange udviklere glemmer dette emne når jeg spørger ind til det til jobsamtaler. De opfatter det blot som noget ubrugeligt fra studiet.

Hvis man ikke forstår algoritmic complexity, så skal man holde sig væk fra større mængder data. Og hvis man ikke roder med større mængder data, så har man ikke behov for at spekulere over performance. [Famous last words™]

Mike Dunlavey's metode er en effektiv metode til dette arbejde (og så simpel at meget få tror på at det fungerer... men det gør det!).

I mine øjne skal en udvikler overveje inden har skriver første linje hvad der sker med hans kode når mængden af data vokser og allerede på det tidpunkt overveje fornuftige data structurer og algoritmer. Når han så har skrevet koden, så er en profiler uundværlig til at finde alle de steder hvor han tog fejl eller havde overset noget.

Men som udvikler kan man spare meget tid hvis man forstår algoritmic complexity af den kode man skriver og de libraries man bruger. Hvis man samtidigt har en forståelse omkring størrelsesordenen af det data man skal arbejde på så er man i god position til at vælge rette løsninger uden at det gør løsningen dyrere eller mere kompleks.

Et meget simpelt eksempel på ovenstående er string concat i C#/Java. Hvornår skal man bruge + operatoren og hvornår skal man bruge stringbuilder/buffer/whatever. I praksis er det kun ved lange strenge at det gør en forskel hvilken man vælger. Ved korte strenge bør man således gå efter hvad der er nemt og let læseligt.

38
16. juni 2020 kl. 22:51

Ja, ofte er det meste optimering helt at eliminere (undgå at udføre) unødvendige operationer fremfor at udføre de eksistererende hurtigere.

Det beskrevne giver sig ofte udtryk i kvadradisk opførsel - det fungerer fint i mindre skala, men ikke på større. Et eksempel er serialisering under .NET (hvis man ikke forhindrer det, bliver der under overfladen, med et udenbys ord, brugt reflection).

Der findes et navn for det på udenbys: Shlemiel the Painter’s Algorithm

Jeg har arbejdet på flere forskellige systemer hvor det med relativ lille indsats lykkedes at få det til køre 20-40 gange hurtigere, vel at mærke dele af systemet som brugerene direkte blev udsat for (f.eks. reduktion af svartider fra minutter til få sekunder).

Mike Dunlavey's metode er en effektiv metode til dette arbejde (og så simpel at meget få tror på at det fungerer... men det gør det!).

37
16. juni 2020 kl. 21:40

Men der er altid tid til fejlsøgning og fejlretning senere?

36
16. juni 2020 kl. 13:06

Se den første lektion - og gerne flere af de efter følgende - af kursus 6.172 (Performance Engineering of Software Systems) fra MIT. 2018 udgaven er forbebedret i forhold til 2010 udgaven, i og med, sourcen er inkluderet til de væsenligste "hacks". Der er dog stadig "fiduser", som kun kan komme fra folk med praktisk erfaring f.eks. omtales transponering af B matricen (i A x B = C) i begge udgaver. Det kalder MIT for MITPOSSE ;-)

Som bekendt er ca halvdelen af spørgsmål i teknisk support om ydelse, hvor man har den udmærkede vane at afprøve løsninger - før de gives videre.

I-cachen er vigtigst fordi alle instruktioner skal igennem der - inden de udføres, mens kun hver femte eller hver tredie instruktion bruger D-cachen. Level-1 cache er typisk 8-ways og utallige interrups luger konstant ud i hvad den indeholder. Det handler om at "genbruge" cache indholdet - mens det er i L1 (løkkesegmentering osv.).

35
16. juni 2020 kl. 12:30

Hvis gjort rigtigt, så er parallel programmering ikke specielt kompliceret. Problemet er, at de fleste programmører er så indgroede i en tankegang med muterbare variabler og sekventiel kode, og den tankegang er gift for parallelisme.

Men hvor mange større systemer kender du der er lavet uden muterbare variabler? Læg så oveni at der nedenunder overfladen i runtimen stadigvæk stadigvæk er en CPU med registre og memory som hele tiden muteres. Og at der stadigvæk er en hel del synkronisering mellem cores som spiser mange ressourcer.

Parallelprogrammering er kompliceret hvis målet er at opnå højere effektivitet. De fine abstrationer som vores programmeringssprog giver os har en tendens til at kollapse fordi nogle løsninger er mere effektive end andre uden at det er synligt for programmøren. Fx. er NUMA typisk usynligt men kan have stor betydning i multiprocessor løsninger.

34
16. juni 2020 kl. 11:27

Det kunne være skægt, at se source koden til det optimerede program. Er der noget der har fundet det? Det står ikke i deres paper.

33
16. juni 2020 kl. 11:17

Effektiv i tid - jo, det er nok rigtigt, effektiv i strøm - måske ikke rigtigt

Der er altid et trade-off mellem tid og energiforbrug. Det koster ganske enkelt mere energi at skifte en tilstand hurtigt end at gøre det langsomt. Så hvis du vil gøre noget meget energieffektivt, så skal du nedsætte clockfrekvensen til kiloHertz området.

Men man behøver ikke at gå til sådanne yderligheder for at spare strøm: En meget stor del af strømforbruget i en moderne CPU-kerne går til spekulativ udførsel, branch prediction, og andre ting, der ikke øger den mængde arbejde, der bliver udført, men blot undgår pauser ved at gætte sig til hvilket arbejde, der skal udføres, udføre det, og så smide det væk igen, hvis gættet var forkert. Out-of-order execution kræver også en del strøm til administration. Ved at lave kernerne meget enklere mister man en del hastighed, men bruger også meget mindre strøm. Det ekstreme eksempel på dette er grafikprocessorerne, hvor kernerne er ekstremt simple (der er f.eks. ikke betingede hop, kun betinget udførsel af instruktioner, og en gruppe af kerner udfører de samme instruktioner samtidigt). Hver kerne bruger meget lidt strøm, og det er kun fordi, der sættes hundrevis af kerner på en chip, at denne chip bruger meget strøm.

Så hvis du vil optimere for strømforbrug, så læg beregningerne på grafikprocessoren. Du skal dog sørge for, at du holder en stor procentdel af kernerne aktive, ellers vinder du hverken tid eller energi.

31
16. juni 2020 kl. 09:23

[...] med fremkomsten af internettet, IoT og generelt mere og mere online-udstyr, har udviklere bedre muligheder for at lave live-patching og opdatering, og dermed bliver kvaliteten af software lavere, da de ikke i første omgang er nødsaget til at lave ordentlig kode.</p>
<p>Det "problem" er ikke nær så kritisk idag, og derfor opprioriteres hurtig udvikling højere.</p>
<p>Er der nogle i branchen der kan be/afkræfte denne hypotese?

Ja. Som en del af mit arbejde træffer jeg beslutninger om, hvilke features, der skal med i vores produkter, og hvad der er godt nok. Heri indgår også en vurdering af mange sofwarekvalitets-parametre som fx fejlrate, performance, strømforbrug, robusthed og sikkerhed. Jeg stræber efter en høj kvalitet, hvor defekt er absolut uacceptabelt, men perfekt ikke altid er nødvendigt.

Det er et forretningsmæssigt valg mellem at:

  • komme tidligt på markedet med et godt nok produkt, eller
  • komme sent på markedet med det perfekte produkt.

Eller den ekstreme udgave:

  • komme meget tidligt på markedet med et halvfærdig produkt.

Omkostningerne ved at ændre software på et solgt produkt er relativt lav, og med OTA- (over the air) opdateringer er det næsten gratis. Så ja, det flytter generelt den forretningsmæssige beslutning i retning af at komme tidligere på markedet med et mere umodent produkt.

De teknologiske landvindinger vedr. software-opgraderinger har altså gjort det muligt at vælge både godt nok nu og perfekt senere.

Det har den yderligere fordel, at producenten kan få feedback om, hvilke ændringer, kunderne - når de har vænnet sig til at bruge produktet - opfatter som vigtige for at perfektionere produktet. Og det kan være helt andre ændringer, end producenten oprindeligt troede.

Tesla kom tidligt på markedet med en elbil, der på enkelte punkter kun var god nok. Folkevogn har indtil for nylig troet, at de skulle vente med at sende deres ID.3-elbil på markedet, til bilen var perfekt; men har nu erkendt, at det er vigtigere at komme på markedet med et godt nok produkt nu, end at vente til produktet er perfekt.

Hvis man venter med at lancere sit produkt til det er helt perfekt, risikerer man at vente for længe.

PS: Jeg hørte engang, at for kunstnere var det en vigtig egenskab at kunne finde det rigtige tidspunkt at stoppe arbejdet på et kunstværk og erklære det færdigt.

mvh

Morten Brørup, CTO, SmartShare Systems

30
16. juni 2020 kl. 02:09

Ak ja, det måtte jo ske en dag. Version2 har fundet MIT! Så mangler vi bare resten af danskerne ;-)

SPE (software performance engineering) dækker over 3 paradigmer: løkkesgementering, afbalancering af IO og processor og sorterting af "request-køer". Uden om samtlige løkker kører en timer, som bruges til af finde det antal elementer, som giver kortest totaltid per element. Ikke alle algoritmer er lige lette at optimere.

I stedet for at gøre programmerne 10 gange hurtige (på given hardware) kan man sænke klokfrekvensen, så de KUN er 5 gange hurtigere, men bruger mindre strøm!

Der kan files meget på koden, men skal det bruges til andet end benchmark kørsler, skal der mere til: Test Driven Development (TDD). Skriv tests før du koder! Begynd med at teste "bladene" og arbejd opad; inkluder tester i starten af programmet, så finder programmørene evt fejl før brugerne.

Artiklen lader til at være baseret på MIT kursus 6.172, som findes på https://OCW.MIT.EDU, men kurset har eksisteret de sidste 10 år - mindst. (Charles E Leiserson: 'your mileage may vary', ja, ham fra "Introduction to Algorithms":)

29
15. juni 2020 kl. 22:27

Sammenligningen er da fint rimelig, hvis de to sprog bliver brugt til det samme. Dovenskaben kunne bestå i at man bare hiver det (Python) ned fra hylden fordi det er det man lige kender. En del af løsningen er også at vælge det rette værktøj til formålet. Python er godt til nogle ting, ikke det rette valg til andre ting.

Som arbejdsgiver for programmører (jeg er selv programmør), og som leverandør til kunder med den slags kode vi skal integrere mod, kan jeg sagtens genkende den tankegang der beskrives. Jeg tror en af årsagerne netop er at mange softwareudviklere i dag ikke har været igennem den mere spæde barndom af programmering. Hardware der regnede så du nærmest kunne følge med i hovedet, targets der havde få kB RAM osv. Da jeg startede med at spille computerspil måtte jeg selv programmere spillet først, og det forsvandt igen når man tog strømmen. Ville jeg have plads til 2-3 farver eller en sprite ekstra, så måtte jeg finde en kløgtig måde at bage lyd-algoritmen ind i anden kode. Den slags udfordringer har de færreste uddannet de seneste 10 år måttet løse.

Det er virkeligt stærkt at vi bygger modulær software på modulær hardware i dag, men vi skal stadig have respekt for at hver byggeklods skal være lavet håndværksmæssigt godt. Og så er alting ikke en hammer, blot fordi man kun kan se søm.

28
15. juni 2020 kl. 18:17

Når man skal optimere kode så er startstedet altid algoritmic complexity. Det er den store hammer og indtil man har udnyttet alt potentiale der, så er der ingen grund til at kigge på andre værktøjer. Algoritmic complexity bør man kigge på allerede i designfasen - dvs. inden der er skrevet blot en linje kode.

Herefter kan man så begynde på parallelisering. Først ved at udnytte flere kerner, subsidiært ved at udnytte flere computere. Ved parallelisering så er det altid tillokkende at gemme parallelisering inde i libraries sådan at brugeren ikke kan se det. Det er næsten altid den gale vej. Det er ofte ineffektivt og øger komplexiteten af libraries.

I eksemplet med matricer, så er det ganske rigtigt at hvis man kun har nogle få matrix multiplikationer der skal udføres, så giver det mening at parallelisere selv multiplikationen. Men hvis man har tusinder af multiplikationer der skal udføres, så kan man passende sprede disse ud på mange kerner/computere der hver sekventielt regner multiplikation - en ad gangen på en enkelt kerne.

På den måde reducerer man komplexiteten, men vigtigere så stiger effektiviteten. For et af problemerne ved parallelprogrammering er at synkronisering og kommunikation mellem tråde er urimeligt dyrt i forhold til almindelig afvikling af instruktioner. Man bør således finde en metode til at løse problemet der sikrer at alle kerner spinner hele tiden (dvs. man må aldrig vente på noget der kan tage tid) og i øvrigt holde synkronisering på et minimum.

Til allersidst kommer optimering vha. specialhardware og avancerede instruktioner. (fx. vektor instruktioner). Problemet med specialhardware er at programmeringssprogets abstraktion typisk falder sammen sådan at udvikleren er nødt til at forstå præcist hvad der sker under overfladen og hvor ganske små ændringer kan få performance til at gå helt i skoven.

Med hensyn til effektiviteten af parallelisering, så er en kerne der kan udføre 2N instruktioner stort set altid bedre end to kerne der hver kan udføre N instruktioner. Desværre har vi nærmet os toppen for clock frekvens og super-scalering så flere kerner og parallelisering er vejen frem.

27
15. juni 2020 kl. 18:06

Som Martin Slot nævner heeeeeeeelt oppe i første post, bliver udviklere ofte henvist tilen rolle som feature factories (genialt udtryk btw). Jeg har selv siddet i roller der kunne beskrives med det udtryk.

Jeg har selv oplevet at få skæld ud af overordnede både for at lave diagrammer, design noter, dokumentation, benchmarking, test og libraries - mest fordi de så de steps som spild af tid. Ikke desto mindre er der rigeligt med shortcuts og teknikker i de forskellige IDEs som er ti gange hurtigere og sikrere end hvad langt de fleste kan lave selv.

Omvendt har jeg i jobsøgningen også mødt en del virksomheder (Webfirmaer og konsulentbureauer især) der tager ekstra betaling for vigtige grundsten i projektet, og jeg har oplevet at få forklaret op i mit åbne, måbende, ansigt at jeg, som udvikler, ikke måtte skrive dokumentation i koden medmindre kunden havde betalt for det. Ved at være for grådige med at levere en vis kvantitet, reducerer de ofte kvaliteten af arbjede - og det går ud over ikke bare kvaliteten, skaleringen og holbarheden af kodebasen, men også ud over deres mulighed for at levere mere kode og features i samme pakke.

Med custom libraries kan man jo netop bygge en porteføjle op af gennemtestede features, som kan indsættes i projektet. Det forhindrer samtidig at programmøren skal starte fra scratch hver gang.

Af og til har jeg også oplevet projekter der kom ud i en meget ufærdig tilstand - faktisk på grænsen til at være ubrugelige. I mine øjne skader det ikke kun projektet - men også virksomhedens omdømme.

At der samtidig ofte efterspørges folk der kan 5-7 forskellige programmeringssprog er igen den indsnigen af kvantitet frem for kvalitet.

Du kan sagtens finde en på 25 som kan C#, Python, Java, Javascript, samt et hav af .js frameworks. Men du kan bide spids på at han/hun ikke kender sprogene godt nok til at skrive særlig optimeret og effektiv kode. Selv er jeg 32 og kan C#, en del Java og en smule Python - men jeg har gravet mig dybere ned i C# end så mange andre ville syntes var smart. Hvorfor? For at skrive bedre kode i et udbredt og fleksibelt sprog!

Omvendt sætter jeg mig også hen til mine Commodore maskiner for at afprøve teknikker på begrænset hardware - det i sig selv har været en åbenbaring for mig, lært mig at lure en del tricks, lært mig mere om hardwarens finurligheder og lært mig at "stoppe" mig selv når jeg programmerer.

Begynder-litteratur reflekterer ofte heller ikke hvilke steps der er bedre at tage i koden frem for andre. Selv hjælper jeg en kammerat med et Unity-projekt der er autodidakt i C# - og igen ser jeg mange klassiske begynderfejl. Fx for i stedet forforeach når vi snakker gennemgang af datasæt og arrays, samt ineffektive, selvskrevne søgninger i selvsamme datasæt. Den vigtige forskel er dog at min kammerat lytter til mig og tager ved lære i stedet fr at sætte en mur op om hvordan det skal være.

I sidste ende er det netop udviklere der manglerrigtigt kendskab til deres miljø, og samtidig urealistiske forventninger (som fx også at skulle agere support og sysadmin samtidigt) der får udviklingen i den retning. Du er mange steder simpelthen tvunget til at sprige over hvor gærdet er lavest...

26
15. juni 2020 kl. 16:21

Forskerne har her taget den "lette" tilgangsvinkel, nemlig at se på beregningstiden af en given opgave. Fair nok. Men der er en række andre omkostninger, som ifølge mine professionelle erfaringer sjældent indgår i overvejelserne, som fx stømforbruget som nævnt i en af kommentarerne. Men der er mange andre aspekter. Jeg har gennem mange år beskæftiget mig med drift af IT-systemer i større organisationer og bl.a. noget så "simpelt" som antivirus (hvilket slet ikke er simpelt i store netværk). Og her har jeg set adskillige eksempler på, hvordan dårlig kode har gjort systemerne stort set ubrugelige, når der blev skalleret op til produktionsniveau. Nogle eksempler:

  1. Et system skal importere filer fra en mappe, som andre systemer uploader til. Et given modul skal bruge én bestemt fil, men i stedet for at læse bare denne ene fil, indlæses** samtlige** filer i mappen, men kun indholdet af den ene fil bruges. Det fungerer fint, sålænge der kun er få filer i mappen. Men når der pludselig er 50.000+ og når man så lige laver AV-skanning af alle de filer, der åbnes, så er der pludselig nogle tidsgrænser, der overskrides - og systemet fejler.

  2. Et system skriver til en logfil, der ligger et pænt stykke nede i filstrukturen. I stedet for at håndtere en evt. fejlsituation i tilfælde af, at logfilen pludselig ikke længere eksisterer, så havde programmøren kodet det således, at først checkes om 'C:' eksisterer, dernæst om 'C:\Folder1' eksisterer, dernæst om 'C:\Folder1\Folder2' eksisterer osv., 6-7 niveauer ned i filstrukturen, for til sidst at checke om selve logfilen eksisterer. Da det til enhver tid kunne tænkes, at der var behov for at skrive til logfilen, blev logfilens eksistens checket 7000 gange i sekundet - når systemet ikke lavede noget. Det gav i alt ca. 35.000 tilgange til filsystemet pr. sekund, altså når systemet ikke lavede noget og** ikke** havde behov for at skrive i logfilen. Når AV-programmet så blev konfigureret til også at skanne mapper (og hvis du mener, at det ikke giver nogen mening, så læs noget om Alternate Data Streams eller Data Forks), så kørte systemet så tungt, at omkring 30% af alle transaktioner fejlede. I dette specifikke tilfælde blev udviklerne faktisk konfronteret med de utilsigtede konsekvenser af deres kodning - det kom som en total overraskelse for dem, at man overhovedet kunne monitorere på performance af deres kode (!).

  3. En række udviklede arbejdede på et større java-baseret system bestående af et stort antal forskellige moduler. Adskillige moduler brugte (tilsyneladende) samme sprogmodul, hvorfor hver enkelt udvikler havde tilføjet sprogmodulet til sit modul, som så blev indlejret som et samlet pakke i det samlede projekt - med det resultat, at dette sprogmodul var indlejret i projektet mere end 20 gange. Det samme gjaldt for et antal andre moduler. Resultat: Kode der reelt fyldte måske 10-20 MB, fyldte nu næsten 1 GB. Da det var et samarbejdsprojekt, lå der (selvfølgelig) et antal versioner, som åd adskillige TB diskplads på NAS-systemet. For hver gang udviklerne, hvoraf nogle "selvfølgelig" sad på den anden siden af Jorden, ændrede den mindste smule, var netværket ved at gå i knæ. Og når de enorme filer så lige blev udsat for AV-skanning, så blev udviklerne utålmodige og dobbeltklikkede på de samme filer igen og igen (altså mange gange i sekundet - !), indtil det punkt, hvor AV-skannerne, NAS-systemet og hele netværket nærmest lå ned.

Ovenstående tre eksempler er fra det virkelige liv. Og det gennemgående træk er, at alle genvordigheder kunne være undgået fuldstændigt, hvis udviklerne havde udvist bare en smule klassisk dyd og optimeret på diskpladsen, hukommelsesforbruget, processorforbruget og båndbredden. Og så var der såmænd nok også blevet sparet noget strøm.

25
15. juni 2020 kl. 15:39

så er det vist ikke ualmindeligt, at man først får løsningen til at køre. Dernæst ser man på, hvor godt den faktisk kører. Det er måske rimeligt nok, en programmørtime er ret dyr sammenlignet med maskintid. Især maskintid på andres maskiner. Det vil også være fjollet at optimere på en rutine, man TROR er tung, men som faktisk betyder meget lidt i det samlede regnskab for køretid/strømforbrug.

Der er helt sikkert nogen der falder i den fælde, jeg kommer til at tænke på følgende udsagn (læs gerne linket):

Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.

24
15. juni 2020 kl. 13:59

så er det vist ikke ualmindeligt, at man først får løsningen til at køre. Dernæst ser man på, hvor godt den faktisk kører. Det er måske rimeligt nok, en programmørtime er ret dyr sammenlignet med maskintid. Især maskintid på andres maskiner. Det vil også være fjollet at optimere på en rutine, man TROR er tung, men som faktisk betyder meget lidt i det samlede regnskab for køretid/strømforbrug.

Men der mangler to ting.

  1. At tænke sig lidt om, før man koder. Hvis man skal læse flere tabeller forfra og bagfra, bør man overveje, om det er nødvendigt. Tilsvarende gælder andre forhold. Tænk i stedet for at brute-force sig til målet.
  2. Følge op på programmets ressourceforbrug - inden man går i produktion og inden brugerne klager.

Jeg gætter på, at det halter med begge dele.

23
15. juni 2020 kl. 13:36

Det er ellers noget af det man ofte bruger Python til, med lidt hjælp fra biblioteker som: 'Numpy', 'Pandas', 'Dask' og 'Theano'.

Det anfægter jeg heller ikke, og det er da også kun smart at benytte specifikke hjælpebiblioteker, som så er en eller anden form for kompileret binary, men det ændrer stadig ikke på at indgangsforudsætningen var en anden.

Og det var i den kontekst jeg mente at sammenligningen var urimelig, ikke for at desavouere Python.

20
15. juni 2020 kl. 13:09

ikke gør koden mere effektiv?

Effektiv i tid - jo, det er nok rigtigt, effektiv i strøm - måske ikke rigtigt

I øvrigt er der andre problemer end at multiplicere matricer, der kan angribes mere eller mindre ineffektivt. Og her er det måske ikke muligt at sprede udførelsen over flere kerner.

18
15. juni 2020 kl. 12:53

Sålænge man ikke benchmark tester sprogene og programmerne, kommer der ingen fremskridt. Det er vi mange der har efterspurgt meget længe. Men kombatiliteten kom åbenbart først HVER GANG.

16
15. juni 2020 kl. 12:42

Forestil dig at du for 25 år siden skulle lave embedded systemer til gud ved hvad - ja så skulle du gøre det rigtigt, for når først koden forlod dit 'bord' så ville det være svært (og omkostningsrigt) at udbedre eventuelle fejl. Det "problem" er ikke nær så kritisk idag, og derfor opprioriteres hurtig udvikling højere.</p>
<p>Er der nogle i branchen der kan be/afkræfte denne hypotese?

Det er rigtigt for den software tunge konsum elektronik (TV, mobil, tablet), men den simple elektroniske radiator termostat eller mikroovn kan man jo ikke lige opgradere.

Det efterlader faktiske endnu en problemstilling vedrørende garanti. Jeg har flere gange været ude for fejl på apparater (f.eks. fladskærme), hvor sælger påstod at tuneren i apparatet var "gået i stykker", og da garantien var udløbet, var køber klar til at kassere apparatet. Køber gjorde så store øjne da fejlen forsvandt efter en nulstilling af softwaren.

Software fejl var jo beviseligt i apparatet på købstidspunktet og derfor blive de holdt hemmelige. Istedet burde producenten tvinges til at offentligøre softwarefejl (samt kildekoden ved supportens udløb hvis det stod til mig).

15
15. juni 2020 kl. 12:23

Oftest er der mere end én kerne der har ledig tid - og så giver det mening af tillade dig at køre på flere kerner - da du så kan udnytte disse (istedet for at de andre kerner idl'er, imens du er længere tid om at få dit lavet færdigt).

Mht. at stjæle ressourcer fra andre, så har man jo schedulering i kernen, til at sikre at du ikke får lov at tage ressourcer fra højere prioriteret opgaver.

14
15. juni 2020 kl. 12:20

Tillad mig et forståelses-spørgsmål her: Hvis jeg skal lave en tung beregnings-funktion og har mulighed for at bruge flere CPU-kerner - opnår jeg da en forbedret ydelse, der er større end svarende til at dividere beregningstiden med antallet af kerner?

Og giver det mening at anvende disse kerner - jeg ville normalt forvente, at yderligere kerner (eller tråde) blev brugt af andre hosts i den virtuelle server, min beregning skal køre på. At jeg så kan rage til mig ved at reservere yderligere kerner betyder vel bare, at disse står unyttige hen, når jeg ikke lige afvikler min beregnings-funktion - altså en suboptimering af det fælles bedste.

13
15. juni 2020 kl. 11:46

af effektivitet, så sådanne sløve algoritmer kan afsløres på en gang og ændres. Men som hr. Mogensen skriver den største udfordring er vel, at der nu er flere kerner men ikke rigtig gode vilkår for at programmere og udnytte dette - det er altså nærmest spild af dyre CPU'er.

12
15. juni 2020 kl. 11:21

Der er gode grunde til, at man normalt bruger bibliotektsfunktioner til multiplikation af store matricer, specielt i sprog som Python:

  1. I Python og andre fortolkede sprog er der er et stort fortolkningsoverhead, som kan forsvinde ved at bruge en biblioteksfunktion, der er skrevet i C eller et andet oversat sprog.
  2. Når datastørrelsen kommer op på 4094×4096×2, kan det ikke altsammen være i primærcachen på en gang, og med den naive tilgang til matrixmultiplikation er meget data forsvundet fra cachen, inden man bruger det igen. Derfor bruger biblioteksfunktioner normalt blocking (http://www.netlib.org/utk/papers/autoblock/node2.html) og cache prefetch.
  3. Biblioteksfunktioner kan udnytte grafikprocessorer til at parallelisere beregningerne.
  4. Der findes matrixmultiplikationsalgoritmer, der i stedet for at køre i O(n³) "kun" bruger ca. O(n^2.8). Det er dog kun for meget store matricer, at det er en fordel.
  5. Hvis matricerne har mange nuller (sparse matrices), findes algoritmer, der er meget hurtigere end den naive. De kræver dog, at nullerne sidder meget systematisk i matricerne (f.eks. at det er øvre trekantsmatricer eller Jacobimatricer).

Så forfatterne har fundet stort set den mest naive måde at lave en tung beregning, og så er det ikke nogen overraskelse, at de får stor speedup ved at bruge oversatte sprog, biblioteksfuntioner og parallelisme.

Men de har en pointe: Vi kan ikke forvente, at computere bliver hurtigere og hurtigere med samme fart som tidligere. Op til for ca, 15 år siden fordobledes clockfrekvensen af single-core CPUer ca. hvert fjerde år. Men så ophørte Dennard scaling (https://en.wikipedia.org/wiki/Dennard_scaling). Siden da er clockfrekvensen holdt nogenlunde konstant, men antallet af kerner i en CPU fordobles ca. hvert fjerde år. Derfor bliver beregningstunge programmer nødt til at bruge parallelisme.

Men de klassiske sprog såsom C, Java, og lignende er ikke født til at udnytte parallelisme, og den gængse model for parallelisme i disse sprog (shared memory) skalerer ikke ret godt (på trods af teknikker som transactional shared memory). Dertil kommer, at den største regnekraft fås fra GPGPUer (general-purpose graphics processing units), som bruger en regnemodel, der er langt fra C og Java, og som derfor normalt programmeres i CUDA eller OpenCL, som er så svære at bruge, at det er nogle få eksperter, der skriver velkendte algoritmer (såsom matrixmultiplikation) i disse sprog, og tillader andre sprog at kalde dem som biblioteksfunktioner. Men hvis der ikke allerede findes en omhyggeligt skrevet parallel udgave af algoritmen, så er 99,99% alle programmører ude at svømme, for de vil ikke have en chance for at omskrive den til en effektiv algoritme til GPGPUer. Der bliver heldigvis udviklet højniveausprog, der gør arbejdet lettere, men de ligner ikke C eller Java ret meget. Et godt eksempel er Futhark (https://futhark-lang.org/), der er udviklet på DIKU.

Men GPGPUer er kendt for at bruge meget strøm, og det bliver kun marginalt bedre, når man skrumper teknologien. Så på sigt skal man bruge andre teknologier end CMOS.

11
15. juni 2020 kl. 09:44

C er derimod et kompileret sprog, så sammenligningen er da ikke rimelig.

Jeg tror at sammenligningen er for at demonstrere "når det eneste værktøj man kender er en hammer så ligner alt et søm"-problematikken. Der er masser af mulige løsning på et problem men mange af de løsninger er kun fornuftige på papiret, desværre bliver de alligevel brugt i praksis - lidt ligesom når man lærte om rekursion på uni i 80'erne, hvor læren var "det er meget muligt det er skide smart men det skal nøje overvejes om det nu også er den bedste løsning".

10
15. juni 2020 kl. 09:34

...og det er ikke altid "udviklere" som er årsagen! Meget ofte er der krav til platform, portabilitet eller udviklingsmiljø som afgører hvad man koder i. De krav bliver sat "Strategisk" af folk som intet aner om udvikling og ydelse (og sikkerhed) efter råd fra konsulenter som ofte har lige så lidt viden som den de rådgiver eller har andre mål end den optimale ydelse.

9
15. juni 2020 kl. 09:30

Dette betyder, at kørselstiden er mere end 47 gange hurtigere end i Python.

Mig bekendt er Python et fortolket sprog, og givetvis ganske anvendeligt til mange ting hvor der ikke skal knuses tal i større mængder.

C er derimod et kompileret sprog, så sammenligningen er da ikke rimelig.

8
15. juni 2020 kl. 08:55

I betragtning af at computere efterhånden står for en stor del af vores el-forbrug, er der også en grøn vinkel på dette.

Ser man bort fra parallelisering, som jo ikke gør koden mere effektiv, kan man altså spare kloden for en procentandel af elforbruget, blot ved at træffe de rigtige designvalg og skrive god kode.

Hvis vi samtidigt blev bedre til at modularisere (Som man oftere tilstræber i Unix miljøer) frem for at producere featurerige monolitter, er der potentiale for mere energi-rigtig og mere driftsikker kode.

7
15. juni 2020 kl. 08:35

På datalogi havde vi i sin tid en programmeringsopgave som pædagogisk fejlede fuldstændigt: det viste sig at daværende java 1.4.2 JIT jre tilsynelandende havde indbygget dynamisk programmering så den huskede resultatet af funktioner der kun afhang af parametren. På den måde sparede java os for at se de forventede eksponentielt voksende køretid af gentagne overflødige rekursive kald - og hele ideen ved selv at lave dynamisk programmering forsvandt derfor.

Den slags skal man også huske når man sammenligner køretid på tværs af sprog og implementationer.

4
15. juni 2020 kl. 08:26

En smart udviklingsmodel er jo typisk at kode nemt og let gennemskueligt og så identificere flaskehalsene (hvor sidder vi og venter) og så optimere koden der.

Men bagsiden er så den "omvendte Moores lov": Softwaren vil ofte udvikle til at forbruge alle ledige systemresurser...

3
15. juni 2020 kl. 07:57

som gik ud på dette: at med fremkomsten af internettet, IoT og generelt mere og mere online-udstyr, har udviklere bedre muligheder for at lave live-patching og opdatering, og dermed bliver kvaliteten af software lavere, da de ikke i første omgang er nødsaget til at lave ordentlig kode.

Forestil dig at du for 25 år siden skulle lave embedded systemer til gud ved hvad - ja så skulle du gøre det rigtigt, for når først koden forlod dit 'bord' så ville det være svært (og omkostningsrigt) at udbedre eventuelle fejl. Det "problem" er ikke nær så kritisk idag, og derfor opprioriteres hurtig udvikling højere.

Er der nogle i branchen der kan be/afkræfte denne hypotese?

2
15. juni 2020 kl. 07:28

En skam de ikke prøvede med PyPy.

Jeg lavede en lille test, men med 600 i stedet for 4096.

Python3: 1m12.195s PyPy3: 0m2.135s

Speedup: 33 gange med ingen indsats.

Bemærk: Python er skrevet i C, PyPy er skrevet i Python. Her er Python altså 33 gange hurtigere end C

1
15. juni 2020 kl. 07:16

Overskriften siger alt. Kvalitet er ikke noget man måler på idag. I dag går der hurtigt politik i udviklingen. Udviklernes gæt bliver omregnet til timer i et resource ark så flest mulige timer kan presses sammen, og de høje herre kan få mest mulig profil trukket ud af forretningen. Det der engang var udviklingsafdelinger, er i dag feature factories. Nytnytnyt. Vi udviklere har ikke tid til at rette op på ændringer i forløbet, hvilket skaber halve løsninger, og vi lærer derved aldrig af vores fejl. Vi skal "ramme" rigtigt første gang.