Framtidens programmeringsspråk utför flera operationer på en enda rad?

Permalänk

Framtidens programmeringsspråk utför flera operationer på en enda rad?

Jag gillar C++. Det är snabbt och kraftfullt samt att gör man ett projekt i C++ så kommer projektet överleva långt trots uppdateringar. Men jag märker att språket börjar allt mer och mer bli komplext då det kommer in nya funktionaliter.

Oftast är det funktioner som utför allt på en enda rad kod.
Då börjar jag se likheter mellan C++ och MATLAB/R/Python där allt kan utföras på en enda kodrad.

Är detta liksom grejen med C++ nya funktioner i C++23?
Om du vill summera ihop två vektorer där ena ska vara upphöjt till 3 så finns det en superoptimerad funktion för detta?

Jag säger inte att det är fel att ha dessa funktioner. Men att komma ihåg dessa funktioner och kunna använda dom känner jag kommer bli en utmaning. C++ växer enormt.

Notera att även Java börjar gå denna väg också. Java 20 har också flera funktioner som utför typ många operationer på en enda kodrad.

Är detta som är framtiden med programmeringsspråken?

Permalänk
Medlem

För oss mindre insatta.
Kan du ge exempel på vad du menar?

Visa signatur

www.fckdrm.com - DRM år 2024? Ha pyttsan.

Permalänk
Medlem
Skrivet av heretic16:

Jag gillar C++. Det är snabbt och kraftfullt samt att gör man ett projekt i C++ så kommer projektet överleva långt trots uppdateringar. Men jag märker att språket börjar allt mer och mer bli komplext då det kommer in nya funktionaliter.

Oftast är det funktioner som utför allt på en enda rad kod.
Då börjar jag se likheter mellan C++ och MATLAB/R/Python där allt kan utföras på en enda kodrad.

Är detta liksom grejen med C++ nya funktioner i C++23?
Om du vill summera ihop två vektorer där ena ska vara upphöjt till 3 så finns det en superoptimerad funktion för detta?

Jag säger inte att det är fel att ha dessa funktioner. Men att komma ihåg dessa funktioner och kunna använda dom känner jag kommer bli en utmaning. C++ växer enormt.

Notera att även Java börjar gå denna väg också. Java 20 har också flera funktioner som utför typ många operationer på en enda kodrad.

Är detta som är framtiden med programmeringsspråken?

En sak man får tänka på är att då man gör allt "rörigt" så måste man kommentera bättre och mer.

Permalänk
Medlem

Det är så alla programmeringsspråk redan fungerar. En CPU kan utföra ett väldigt begränsat antal operationer.

Visa signatur

Bra, snabbt, billigt; välj två.

Ljud
PC → ODAC/O2 → Sennheiser HD650/Ultrasone PRO 900/...
PC → S.M.S.L SA300 → Bowers & Wilkins 607

Permalänk
Medlem

Snackar du om att injecta en functionspekare istället för att köra en loop eller?

Permalänk
Medlem
Skrivet av heretic16:

Jag gillar C++. Det är snabbt och kraftfullt samt att gör man ett projekt i C++ så kommer projektet överleva långt trots uppdateringar. Men jag märker att språket börjar allt mer och mer bli komplext då det kommer in nya funktionaliter.

Oftast är det funktioner som utför allt på en enda rad kod.
Då börjar jag se likheter mellan C++ och MATLAB/R/Python där allt kan utföras på en enda kodrad.

Är detta liksom grejen med C++ nya funktioner i C++23?
Om du vill summera ihop två vektorer där ena ska vara upphöjt till 3 så finns det en superoptimerad funktion för detta?

Jag säger inte att det är fel att ha dessa funktioner. Men att komma ihåg dessa funktioner och kunna använda dom känner jag kommer bli en utmaning. C++ växer enormt.

Notera att även Java börjar gå denna väg också. Java 20 har också flera funktioner som utför typ många operationer på en enda kodrad.

Är detta som är framtiden med programmeringsspråken?

Jag misstänker att det du syftar på är builder pattern i kombination med funktionell programmering?

Exempelvis

(0..9).into_iter().map(|x| x*2).collect();

Permalänk
Datavetare

Det låter rätt mycket som java-streams och C++-ranges, men då dessa inte är en nyhet i vare sig Java 20 eller C++-23 gissar jag att du inte menade det.

Menar du Java vector API? Fast stämmer inte riktigt heller då jag motsvarande i C++ fortfarande är markerat som experimentellt, kan komma med i C++-26.

Explicit SIMD-stöd har flera orsaker.

Dels måste det vara explicit då det har visat sig vara i praktiken omöjligt att effektivt generera vektoriserade kod för mer än de mest triviala fallen från "vanlig C++, Java, liknande språk".

Dels vill man få ett sådan stöd då x86_64 (SSE, AVX, AVX-512), ARM64 (NEON, SVE, SVE2) och RISC-V håller på att designa motsvarande. D.v.s. CPU-tillverkarna lägger rätt mycket kiselyta på detta så det är önskvärt att utnyttja detta så bra som möjligt.

Ett stort problem med SIMD på x86_64 är att både instruktionerna och dess registerbredd beror på vilken version av stödet man vill använda. Även om ARM64 lyckades ha samma instruktioner oavsett vektor-registerbredd med SVE/SVE2, kvarstår fortfarande anpassningen till registerbredden.

Som jag förstår det har Javas vector API gått från experimentellt till "färdig" i Java 20

Exempel på både streams (som relativt enkelt kan använda flera CPU-kärnor) och vector APU (som kan använda den SIMD bredd som stöd av den aktuella CPUn)

vectorMultiply() är exempel på hur man kan använda SIMD och streamMultiply() är exempel på hur man kan använda flera CPU-kärnor för att multiplicera innehållet i två array:er. (Dåligt exempel för att visa vinsten med teknikerna, men visar hur man använder APIerna).

import java.util.stream.IntStream; import jdk.incubator.vector.*; class App { static final VectorSpecies<Double> SPECIES = DoubleVector.SPECIES_PREFERRED; static double[] vectorMultiply(double[] a, double[] b) { if (a.length != b.length) { throw new IllegalArgumentException("Argument arrays must have the same length"); } double[] c = new double[a.length]; for (int i = 0; i < a.length; i += SPECIES.length()) { VectorMask<Double> m = SPECIES.indexInRange(i, a.length); DoubleVector va = DoubleVector.fromArray(SPECIES, a, i, m); DoubleVector vb = DoubleVector.fromArray(SPECIES, b, i, m); DoubleVector vc = va.mul(vb); vc.intoArray(c, i, m); } return c; } static void simd(double[] a, double[] b) { double[] c = vectorMultiply(a, b); System.out.printf("vector length: %d\n", SPECIES.length()); for (int i = 0; i < c.length; i++) { System.out.println(c[i]); } } static double[] streamMultiply(double[] a, double[] b) { return IntStream .range(0, a.length) .parallel() .mapToDouble(i -> a[i] * b[i]) .toArray(); } static void par(double[] a, double[] b) { double[] c = streamMultiply(a, b); System.out.printf("number of cores : %d\n", Runtime.getRuntime().availableProcessors()); for (int i = 0; i < c.length; i++) { System.out.println(c[i]); } } public static void main(String[] args) { int N = 32; double[] a = new double[N]; double[] b = new double[N]; for (int n = 0; n < N; n++) { a[n] = n + 1; b[n] = n + 11; } simd(a, b); par(a, b); } }

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Skrivet av Yoshman:

Det låter rätt mycket som java-streams och C++-ranges, men då dessa inte är en nyhet i vare sig Java 20 eller C++-23 gissar jag att du inte menade det.

Menar du Java vector API? Fast stämmer inte riktigt heller då jag motsvarande i C++ fortfarande är markerat som experimentellt, kan komma med i C++-26.

Explicit SIMD-stöd har flera orsaker.

Dels måste det vara explicit då det har visat sig vara i praktiken omöjligt att effektivt generera vektoriserade kod för mer än de mest triviala fallen från "vanlig C++, Java, liknande språk".

Dels vill man få ett sådan stöd då x86_64 (SSE, AVX, AVX-512), ARM64 (NEON, SVE, SVE2) och RISC-V håller på att designa motsvarande. D.v.s. CPU-tillverkarna lägger rätt mycket kiselyta på detta så det är önskvärt att utnyttja detta så bra som möjligt.

Ett stort problem med SIMD på x86_64 är att både instruktionerna och dess registerbredd beror på vilken version av stödet man vill använda. Även om ARM64 lyckades ha samma instruktioner oavsett vektor-registerbredd med SVE/SVE2, kvarstår fortfarande anpassningen till registerbredden.

Som jag förstår det har Javas vector API gått från experimentellt till "färdig" i Java 20

Exempel på både streams (som relativt enkelt kan använda flera CPU-kärnor) och vector APU (som kan använda den SIMD bredd som stöd av den aktuella CPUn)

vectorMultiply() är exempel på hur man kan använda SIMD och streamMultiply() är exempel på hur man kan använda flera CPU-kärnor för att multiplicera innehållet i två array:er. (Dåligt exempel för att visa vinsten med teknikerna, men visar hur man använder APIerna).

import java.util.stream.IntStream; import jdk.incubator.vector.*; class App { static final VectorSpecies<Double> SPECIES = DoubleVector.SPECIES_PREFERRED; static double[] vectorMultiply(double[] a, double[] b) { if (a.length != b.length) { throw new IllegalArgumentException("Argument arrays must have the same length"); } double[] c = new double[a.length]; for (int i = 0; i < a.length; i += SPECIES.length()) { VectorMask<Double> m = SPECIES.indexInRange(i, a.length); DoubleVector va = DoubleVector.fromArray(SPECIES, a, i, m); DoubleVector vb = DoubleVector.fromArray(SPECIES, b, i, m); DoubleVector vc = va.mul(vb); vc.intoArray(c, i, m); } return c; } static void simd(double[] a, double[] b) { double[] c = vectorMultiply(a, b); System.out.printf("vector length: %d\n", SPECIES.length()); for (int i = 0; i < c.length; i++) { System.out.println(c[i]); } } static double[] streamMultiply(double[] a, double[] b) { return IntStream .range(0, a.length) .parallel() .mapToDouble(i -> a[i] * b[i]) .toArray(); } static void par(double[] a, double[] b) { double[] c = streamMultiply(a, b); System.out.printf("number of cores : %d\n", Runtime.getRuntime().availableProcessors()); for (int i = 0; i < c.length; i++) { System.out.println(c[i]); } } public static void main(String[] args) { int N = 32; double[] a = new double[N]; double[] b = new double[N]; for (int n = 0; n < N; n++) { a[n] = n + 1; b[n] = n + 11; } simd(a, b); par(a, b); } }

Så alla dessa nya funktioner är anpassat för att vara så optimala som möjligt för en specifik hårdvara?

Till exempel multiplikation för matriser. Istället för att ta två tal åt gången, så kan man ta en hel kolumn och rad direkt?

Men då måste man använda en speciell funktion för detta?

Permalänk
Skrivet av orp:

Jag misstänker att det du syftar på är builder pattern i kombination med funktionell programmering?

Exempelvis

(0..9).into_iter().map(|x| x*2).collect();

Ja. Vad betyder det där liksom.

Jag förstår idén med att skriva allt på en enda rad, men det blir grötigt.

Jag ogillar C++ auto som finns överallt. Är detta något sätt för att komma bort från int, float, long, double, short, uint16_t osv?

Permalänk
Medlem
Skrivet av heretic16:

Ja. Vad betyder det där liksom.

Jag förstår idén med att skriva allt på en enda rad, men det blir grötigt.

Jag ogillar C++ auto som finns överallt. Är detta något sätt för att komma bort från int, float, long, double, short, uint16_t osv?

Det kan naturligtvis vara ett problem om du stöter på något liknande i ett projekt med andra medlemmar, men då kanske det är läge att se över lite projektregler om hur det ska se ut kodmässigt.

Men generellt så betyder inte ny funktionalitet att gamla sätt att koda slutar fungera, men med tiden kanske man ersätter sina gamla sätt om det blir någon form av standard för att lösa ett problem.

"Ren" kod är dock enklare att läsa, helt klart.

Permalänk
Hedersmedlem
Skrivet av heretic16:

Ja. Vad betyder det där liksom.
Jag förstår idén med att skriva allt på en enda rad, men det blir grötigt.

Det går ju även att dela upp det på flera rader så det blir mer läsligt.

(0..9).into_iter() .map(|x| x*2) .collect();

Det må vara en rad för kompilatorn, men för oss människor gör det sig bäst som 3 eller rent av 4 rader, där resultatet i princip sparas i en tillfällig variabel som man slipper skriva ut.

Motsvarande kod som ingen skulle skriva:

val nums = (0..9); val iter = nums.into_iter(); val doubled = iter.map(|x| x*2); val result = doubled.collect();

Skrivet av heretic16:

Jag ogillar C++ auto som finns överallt. Är detta något sätt för att komma bort från int, float, long, double, short, uint16_t osv?

Det är väl snarare ett sätt att slippa ange mer komplexa typer än sådär, typ om man har ett gäng templates.

Ett mer realistiskt exempel:

auto p = std::make_shared<typnamn>(x); // ... istället för ... std::shared_ptr<typnamn> p = std::make_shared<typnamn>(x);

Eftersom typen är uppenbar för programmeraren så är det ingen större poäng att skriva ut den istället för auto.
Typer kan ju bli långt mer komplexa än en enkel pekare, så i vissa fall är det rätt skönt med auto.

Visa signatur

Asus ROG STRIX B550-F / Ryzen 5800X3D / 48 GB 3200 MHz CL14 / Asus TUF 3080 OC / WD SN850 1 TB, Kingston NV1 2 TB + NAS / Corsair RM650x V3 / Acer XB271HU (1440p165) / LG C1 55"
Mobil: Moto G200

Permalänk
Datavetare
Skrivet av heretic16:

Så alla dessa nya funktioner är anpassat för att vara så optimala som möjligt för en specifik hårdvara?

Till exempel multiplikation för matriser. Istället för att ta två tal åt gången, så kan man ta en hel kolumn och rad direkt?

Men då måste man använda en speciell funktion för detta?

Dessa funktioner är designade för att fungera på mer än bara en specifik HW. Vill man designa för en specifik HW har det sedan länge funnits vägar, i C/C++ via intrinsics (Arm, RISC-V etc har motsvarande).

Tar man denna snipet

for (int i = 0; i < a.length; i += SPECIES.length()) { VectorMask<Double> m = SPECIES.indexInRange(i, a.length); DoubleVector va = DoubleVector.fromArray(SPECIES, a, i, m); DoubleVector vb = DoubleVector.fromArray(SPECIES, b, i, m); DoubleVector vc = va.mul(vb); vc.intoArray(c, i, m); }

så är det en högnivåbeskrivning av SIMD som fungerar oavsett om man kör SSE, AVX-512, NEON, SVE eller vad det nu kan tänkas vara. Exakta beteendet samt genererade instruktioner kommer skilja, men ovan beskriver problemet på en "lagom" hög nivå för att man effektivt ska kunna generera "bra" SIMD kod för underliggande HW.

Samma tanke ligger bakom C++ SIMD API. Motsvarande arbete görs även för Rust, är redan del av språket Zig.

Att accelerera matrisoperationer är en sak man kan göra med dessa tillägg till programspråk, men finns långt fler användarfall.

Just matrisoperationer är i sig så exceptionellt användbart att Apple byggt in accelerator de kallar AMX (Advanced MatriX coprocessor) i M1/M2 och gjort det tillgängligt via deras accelerate
ramverk (som även fungerar på x86_64 och tidigare iPhone CPUer, men där används SSE/AVX samt NEON i stället).

Arm har lagt till liknande instruktioner som del av ARMv9, Scalable Matrix Extensions och Intel har precis lagt till AMX (Advanced Matrix Extensions, så samma förkortning som Apple men lite olika approach), finns än så länge bara i serverbaserade Sapphire Rapids.

Så med tiden lär det komma utökningar likt accelerate (som är gjort för Swift) även till Java, C++, etc för att dra nytta av de matrisinstruktioner som nu läggs in i populära CPU-instruktionsuppsättningar.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem
Skrivet av heretic16:

Ja. Vad betyder det där liksom.

Jag förstår idén med att skriva allt på en enda rad, men det blir grötigt.

Jag ogillar C++ auto som finns överallt. Är detta något sätt för att komma bort från int, float, long, double, short, uint16_t osv?

Det är möjligen svårt för dig att läsa eftersom du inte är van vid det. Det är ett vanligt design pattern.

(0..9) skapar en range.
into_iter() konverterar rangen till en iterator.
map() låter dig applicera en funktion för varje värde i rangen.
collect() kör igenom iteratorn och samlar alla värdena i en collection.

Det är ju väldigt kraftfullt och åstadkommer mycket med lite kod.

Permalänk
Medlem
Skrivet av heretic16:

Ja. Vad betyder det där liksom.

Jag förstår idén med att skriva allt på en enda rad, men det blir grötigt.

Jag ogillar C++ auto som finns överallt. Är detta något sätt för att komma bort från int, float, long, double, short, uint16_t osv?

Nu kan jag inte just den där syntaxen men det är väl som fluid interfaces i alla språk, om man inte läsa det så ser det fruktansvärt komplicerat ut men när man kan det så blir det mycket mer läsligt. Påhittat krysstat exempel i C#:

var ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var evenDoubledInts = new List<int>(); foreach(var number in ints) { if(number % 2 == 0) { evenDoubledInts.Add(number*2); } } int totalSum = 0; foreach(var number in evenDoubledInts) { totalSum += number; } double average = totalSum / evenDoubledInts.Count;

var ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; double average = ints .Where(x => x % 2 == 0) .Select(x => x*2) .Average();

Vi beskriver för kompilatorn deklarativt vad vi vill göra och låter den oroa sig för detaljerna, jag vill bara ha de jämna talen, jag vill att de dubbleras och till sist vill jag veta medelvärdet. Hur du gör bryr jag mig inte om.

Permalänk
Datavetare
Skrivet av Xenofonus:

Nu kan jag inte just den där syntaxen men det är väl som fluid interfaces i alla språk, om man inte läsa det så ser det fruktansvärt komplicerat ut men när man kan det så blir det mycket mer läsligt. Påhittat krysstat exempel i C#:

var ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var evenDoubledInts = new List<int>(); foreach(var number in ints) { if(number % 2 == 0) { evenDoubledInts.Add(number*2); } } int totalSum = 0; foreach(var number in evenDoubledInts) { totalSum += number; } double average = totalSum / evenDoubledInts.Count;

var ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; double average = ints .Where(x => x % 2 == 0) .Select(x => x*2) .Average();

Vi beskriver för kompilatorn deklarativt vad vi vill göra och låter den oroa sig för detaljerna, jag vill bara ha de jämna talen, jag vill att de dubbleras och till sist vill jag veta medelvärdet. Hur du gör bryr jag mig inte om.

Vad som är värt att veta här är att kompilatorn ofta har svårare att göra något riktigt optimalt med det sista.

Testar man dina två exempel så är är "foreach" versionen ungefär 2x så snabb som den deklarativa versionen.

Vidare kanske nedan är mer vad man rimligen borde ha skrivit om man siktade på en imperativ version, den är 6x snabbare än den deklarativa versionen (bench:at med BenchmarkDotNet) och .NET 7.0.

var ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; double sumDoubledEvenNumbers = 0; int numEvenNumbers = 0; foreach (var number in ints) { if (number % 2 == 0) { sumDoubledEvenNumbers += number * 2; numEvenNumbers++; } } double average = sumDoubledEvenNumbers / numEvenNumbers;

Prestanda är definitivt inte allt och helt klart kan den deklarativa varianten vara betydligt enklare att förstå i vissa lägen. Men den stilen är inte helt icke-kontroversiell, det är fullt möjligt att skriva kod på det sättet i Golang (finns bibliotek som gör det möjligt) men där är det i stort sätt konsensus att "for-loop" är lättare för andra att förstå och därför att föredra.

Men finns lägen där den deklarativa versionen kan ge prestandafördelar. Java parallel streams, Rust Rayon, standard C++ par/par_unseq execution policies och liknande kan med rätt typ av problem ge en rätt fin prestandaboost som ofta är betydlig svårare att få till med imperativ stil.

Så som det mesta inom programmeringsvärlden, vad som är "bäst" är långt ifrån entydigt...

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk

Jag tror TS pratar om Autocode, Fortan och andra språk som kom på 1950 talet. Jag tror att dessa språk kommer slå igenom och inom snar framtid kommer nästan ingen längre koda maskinkod.
Dessa språk kommer också utvecklas och det kommer bli mer otydligt om det är funktion i själva språket man kodar eller ett bibliotek som man har kopplat på för att utveckla funktionerna.

Språken kommer för varje år utvecklas, folk kommer fundera på hur man kan göra populära operationer på mindre rader kod och dessutom mer lättläst. Vilket kommer leda till att det blir enklare att koda samma sak med en senare version av språket.
LINQ i C# är ett exempel på hur man kan minska ner koden.

Olika språk hittar olika vägar och det går argumentera för vilket som är bäst. Det språk jag mest stör mig på är Powershell som ska ha en egen "syntaxstil". Det går argumentera för att Powershells syntax är den bästa. Men jag och de flesta i världen sitter inte hela dagarna och kodar Powershell hela dagarna och då när man ska koda detta språk så får vi krångla med de mest enklaste sakerna. -Hur gör man en if-sats, forloop etc? Ja, det blir och googla.

Permalänk
Medlem
Skrivet av Yoshman:

Vad som är värt att veta här är att kompilatorn ofta har svårare att göra något riktigt optimalt med det sista.

Testar man dina två exempel så är är "foreach" versionen ungefär 2x så snabb som den deklarativa versionen.

Vidare kanske nedan är mer vad man rimligen borde ha skrivit om man siktade på en imperativ version, den är 6x snabbare än den deklarativa versionen (bench:at med BenchmarkDotNet) och .NET 7.0.

var ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; double sumDoubledEvenNumbers = 0; int numEvenNumbers = 0; foreach (var number in ints) { if (number % 2 == 0) { sumDoubledEvenNumbers += number * 2; numEvenNumbers++; } } double average = sumDoubledEvenNumbers / numEvenNumbers;

Prestanda är definitivt inte allt och helt klart kan den deklarativa varianten vara betydligt enklare att förstå i vissa lägen. Men den stilen är inte helt icke-kontroversiell, det är fullt möjligt att skriva kod på det sättet i Golang (finns bibliotek som gör det möjligt) men där är det i stort sätt konsensus att "for-loop" är lättare för andra att förstå och därför att föredra.

Men finns lägen där den deklarativa versionen kan ge prestandafördelar. Java parallel streams, Rust Rayon, standard C++ par/par_unseq execution policies och liknande kan med rätt typ av problem ge en rätt fin prestandaboost som ofta är betydlig svårare att få till med imperativ stil.

Så som det mesta inom programmeringsvärlden, vad som är "bäst" är långt ifrån entydigt...

Ja just i C#-LINQ är implementationen att först görs din collection om till en IEnumerable, och att varenda metod du sedan kedjar på blir en loop som skapar upp en ny IEnumerable med förändringen (eller aggregerar ihop den som med t ex Average, Sum och så klart Aggregate). Så inge vidare om det är högsta möjliga prestanda man jagar.

Permalänk
Hedersmedlem
Skrivet av Yoshman:

foreach (var number in ints) {

Tyvärr har väldigt många finesser en tendens att kosta i c#; med LINQ är det väldigt lätt gjort att råka skriva något som tar 100 gånger längre tid än den naiva c-liknande loop-lösningen. Bara valet av foreach istället för for ovan ökar körningstiden med 10% tror jag...

Permalänk
Skrivet av Elgot:

Tyvärr har väldigt många finesser en tendens att kosta i c#; med LINQ är det väldigt lätt gjort att råka skriva något som tar 100 gånger längre tid än den naiva c-liknande loop-lösningen. Bara valet av foreach istället för for ovan ökar körningstiden med 10% tror jag...

Jag tvivlar på så enkelt kod om kompilatorn inte optimerar det bättre
Men man får se rent praktisk. Snabbaste koden skrivs självklart med maskinkod, det innebär inte att alla skriver en lösning effektivast i detta, speciellt inte när vi pratar om att man har väldigt kort tid på sig och koden ska vara enkel och billig och underhålla.
Det leder till mer högnivå och man använder allt mer bibliotek. Ens binärkod blir betydligt mer bloatad och man på väldigt små saker tydligt kan se ineffektivitet. Men utvecklingen går emot att man gör allt större saker som man rent praktisk inte hade kodat annars.

Så med LINQ i C# så kan man supersnabbt koda saker och få ett lättläst och superlätt kod. Denna kod hade ibland tagit längre tid och kodat annars och den tid finns ej.

Problemet jag ser är att datorutvecklingen håller på och stagnera, detta samtidigt som man i samma takt vill bygga större applikationer i samma takt. Min åsikt är att vi får se vad som händer. Ai har redan och kan få inverkan i automatgeneringen av kod. Men samtidigt denna massa kodmängd är ej önskvärd.

Det jag inte gillar idag är om man har ett rent enkelt populärt problem så finns det ofta hur många otroligt blotade bibliotek som helst med lösningar på detta, trots att man laddar hem ett megabloatad, så har den ofta inte de funktionerna man vill ha.
Det innebär att man egentligen skulle ha ett ännu mer blotad.
Min åsikt: Vi får se vad som händer i framtiden.. Jag gillar inte att allt ska vara så gigantisk idag, men jag tvivlar på att det ändras.

*edit*
Ett exempel.
OpenXML SDK används för att skapa excel, worddokument etc. Den är riktigt blotad.
Jag tror antalet forumsmedlemar här på forumet som själv kodar ihop en lösning som skapar en Excelfil med lite data själv är väldigt få. Detta trots att det egentligen inte är så supermärkvärdigt, det är några xml filer som är ihopzippade.

Så det effektivaste sätter att koda en lösning på innebär i praktiken inte att det blir den effektivaste sätter i praktiken för i princip alla.

Permalänk
Hedersmedlem
Skrivet av lillaankan_i_dammen:

Jag tvivlar på så enkelt kod om kompilatorn inte optimerar det bättre
Men man får se rent praktisk. Snabbaste koden skrivs självklart med maskinkod, det innebär inte att alla skriver en lösning effektivast i detta, speciellt inte när vi pratar om att man har väldigt kort tid på sig och koden ska vara enkel och billig och underhålla.
Det leder till mer högnivå och man använder allt mer bibliotek. Ens binärkod blir betydligt mer bloatad och man på väldigt små saker tydligt kan se ineffektivitet. Men utvecklingen går emot att man gör allt större saker som man rent praktisk inte hade kodat annars.

Så med LINQ i C# så kan man supersnabbt koda saker och få ett lättläst och superlätt kod. Denna kod hade ibland tagit längre tid och kodat annars och den tid finns ej.

Nja, så långt vill jag nog inte gå. Många språk på hög nivå kan ju leverera utmärkt prestanda (ofta bättre än vad någon realistiskt kan åstadkomma i assembler), och det är klart att det kan finnas plats även för konstruktioner som är snygga snarare än effektiva. Risken är dock att man får ersätta den snygga koden när systemet väl skall levereras.

Permalänk
Medlem

Till er som tycker att det verkar för krångligt med C++ ranges (nytt) använder ni normalt sett några av de moderna aspekterna som rekommenderats i över tio års tid? Exempel:
- Typinferens
- RAII
- unique_ptr / shared_ptr istället för råa pekare
- std-library containers, t.ex. vector framför råa arrayer.
- std-library algorithm.h för att t.ex. göra std::transform, std::accumulate, osv istället för manuellt skrivna for/while-loopar.
- Lokala lambdafunktioner

Kan ju vara så att en del av nymodigheterna gör språket *enklare* att använda istället för mer förvirrande.

Permalänk
Skrivet av Perkoff:

Till er som tycker att det verkar för krångligt med C++ ranges (nytt) använder ni normalt sett några av de moderna aspekterna som rekommenderats i över tio års tid? Exempel:
- Typinferens
- RAII
- unique_ptr / shared_ptr istället för råa pekare
- std-library containers, t.ex. vector framför råa arrayer.
- std-library algorithm.h för att t.ex. göra std::transform, std::accumulate, osv istället för manuellt skrivna for/while-loopar.
- Lokala lambdafunktioner

Kan ju vara så att en del av nymodigheterna gör språket *enklare* att använda istället för mer förvirrande.

Jag håller med om att moderna språk är förvirrande.

Jag menar, vad är det för fel på C? Passar ju allt.

C++98 tycker jag är riktigt bra. Det är som C with Classes. Påminner om gamla "Sams learn C++ in 21 days!" från 2003.

Permalänk
Medlem
Skrivet av heretic16:

Jag menar, vad är det för fel på C? Passar ju allt.

Jag gillar C men det är nog mest den nostalgiska sidan av mig. Man åstadkommer inte särskilt mycket per kodrad. Det är lätt att göra fel. Det saknar stöd för korsplattform och verktygsstödet är inte det bästa något som du själv erfarat.

Permalänk
Medlem

Programmeringsspråk brukar enligt min erfarenhet börja som enkla och smidiga och då locka många användare med de egenskaperna. Men när folk bemästrar språket börjar de störa sig på att behöva skriva mycket repetitiva saker. Lösningen är då att införa fler features som t ex macron, templates, typinferens, continuations, pakethanterare osv osv vilket gör språket mer och mer komplicerat. De som var med från början tycker språket är kanonbra och förstår varför allting infördes och hur mycket tid det sparar, men nybörjare vänder det ryggen och väljer istället något nytt enkelt och smidigt programmeringsspråk...

Permalänk
Hedersmedlem
Skrivet av trudelutt:

Lösningen är då att införa fler features som t ex macron, templates, typinferens, continuations, pakethanterare osv osv vilket gör språket mer och mer komplicerat. De som var med från början tycker språket är kanonbra och förstår varför allting infördes och hur mycket tid det sparar, men nybörjare vänder det ryggen och väljer istället något nytt enkelt och smidigt programmeringsspråk...

Frågan är väl om det måste vara så? Modern c++ kan ju vara rätt nybörjarvänlig jämfört med äldre standarder, och det borde väl vara möjligt att undvika de komplicerade delarna tills man är redo (template-meta-programmering lever ju till exempel de flesta helt ovetande om, men det finns där om det behövs)?

Permalänk
Medlem
Skrivet av heretic16:

Jag gillar C++. Det är snabbt och kraftfullt samt att gör man ett projekt i C++ så kommer projektet överleva långt trots uppdateringar. Men jag märker att språket börjar allt mer och mer bli komplext då det kommer in nya funktionaliter.

Oftast är det funktioner som utför allt på en enda rad kod.
Då börjar jag se likheter mellan C++ och MATLAB/R/Python där allt kan utföras på en enda kodrad.

Är detta liksom grejen med C++ nya funktioner i C++23?
Om du vill summera ihop två vektorer där ena ska vara upphöjt till 3 så finns det en superoptimerad funktion för detta?

Jag säger inte att det är fel att ha dessa funktioner. Men att komma ihåg dessa funktioner och kunna använda dom känner jag kommer bli en utmaning. C++ växer enormt.

Notera att även Java börjar gå denna väg också. Java 20 har också flera funktioner som utför typ många operationer på en enda kodrad.

Är detta som är framtiden med programmeringsspråken?

Ditt exempel går att skriva på en rad om man känner för det med en for loop. Kompilatorn kommer inte ha några som helst problem att autovektorisera koden för att köra den riktigt effektivt.
Det är lite otydligt vad du faktiskt menar med "utföras på en enda kodrad" och "superoptimerad"?

Jag började programmera C++ tidigare i veckan så mina åsikter kommer mer ifrån erfarenhet av andra språk som t.ex. Scala och APL än någon djupare c++ kunskap.
Jag brukar försöka undvika att använda loopar och använder istället de algoritmer som följer med språket. De är vältestade och oftast är de skrivna med effektiv kod. Man slipper enkla slarvfel och man slipper skriva tester för något som redan finns färdigt. Dessutom blir koden lättare att läsa och förstå när man väl lärt sig hur de olika algoritmerna fungerar. Det betyder inte att man alltid ska undvika loopar. Ibland kan de behövas för att skriva riktigt optimerad kod och ibland kan en loop faktiskt vara enklare att läsa om den t.ex. bara innehåller en rad kod som ditt exempel. Med optimerad kod menar jag kod som du med en korrekt benchmark (vilket är väldigt komplicerat) kan bevisa faktiskt är snabbare än den kod du redan har (iaf x2 annars är det oftast inte värt det) och som du har ordentligt med tester för. I nästan alla situationer är standard algoritmerna rätt val eller så finns det redan andra optimerade bibliotek som tex för linjär algebra. Även när jag vet att jag kommer behöva optimerad kod från start så kodar jag först en version som använder standard biblioteket eller programmerar i APL och försöker använda algoritmer som jag vet att det enkelt går att skriva en parallell version av som transform, partial_sum/scan, reduce osv. Sen när jag har en fungerande algoritm så översätter jag koden till t.ex. while loopar och javas vector api för att få en simd optimerad version.

Det jag gissar att det du pratar om som nytt i C++ är ranges och views? Algoritmerna som finns i ranges är inte nya utan har funnits i <algorithm>/<numeric> länge. Kan du C++ 98/11 är det enkelt att börja använda ranges istället. Skillnaden är att du inte längre behöver ange en start/end iterator utan kan skicka in en container direkt, std::ranges::sort(vec) istället för std::sort(vec.begin(), vec.end()). Koden blir enklare. Eftersom ranges använder concepts så får du också aningen bättre felmeddelanden (2 sidor istället för 10). En annan skillnad är att det inte krävs en riktig iterator som end utan det räcker med en sentinel som kan indikera om du nått slutet på din range. Det gör det möjligt att arbeta med ranges utan tydligt slut vilket är användbart i kombination med range adaptors (views) som använder lat evaluering. Views liknar mer hur du skulle programmera i funktionella språk.

T.ex. för att skapa alla jämna kubiktal under 1000 kan du bygga upp en pipeline som ser ut så här:

auto vec = views::iota(1) | views::transform([](auto x) { return x * x * x; }) | views::filter([](auto x) { return x % 2 == 0; }) | views::take_while([](auto x) { return x < 1000; }) | ranges::to<vector>();

Jag är lite tveksam till views eftersom de tyvärr verkar kräva djupare kunskap om hur de rent tekniskt är implementerade vilket är synd.

Ditt exempel "Om du vill summera ihop två vektorer där ena ska vara upphöjt till 3.." kan du skriva så här :

std::transform(v1.cbegin(), v1.cend(), v2.cbegin(), out.begin(), [](auto a, auto b) { return a + b * b * b; }); std::ranges::transform(v1, v2, out.begin(), [](auto a, auto b) { return a + b * b * b; }); std::views::zip_transform([](auto a, auto b) { return a + b * b * b; }, v1, v2) | std::ranges:to<vector>();

Inget av det här är nytt när det kommer till programmeringsspråk. Ta reduce t.ex. som kommer från APL. Det är alltså något som funnits i 60 år. Det som är nytt att man måste skriva en massa text istället för bara +/ eller u + v * 3.

Permalänk
Medlem
Skrivet av Yoshman:

Som jag förstår det har Javas vector API gått från experimentellt till "färdig" i Java 20

Vector API delen av project panama har fortfarande incubator status i väntan på project valhalla (value-types). API:et kan/kommer ändras mellan olika versioner av jdk.

Tyvärr genererar inte indexInRange effektiv kod i en loop utan man skapar en ny mask hela tiden. Som det är nu får man skriva en skalär post-loop (eller indexInRange/mask) manuellt. Använder man vector api:t så rekommenderar jag att man både kör benchmarks och tittar på assemblerkoden som faktiskt genereras.

def mult(xs: Array[Double], ys: Array[Double], out: Array[Double]) = val dsp = DoubleVector.SPECIES_PREFERRED val len = xs.length min ys.length min out.length val bound = dsp.loopBound(len) var i = 0 while i < bound do DoubleVector.fromArray(dsp, xs, i).mul(DoubleVector.fromArray(dsp, ys, i)).intoArray(out, i) i += dsp.length while i < len do out(i) = xs(i) * ys(i) i += 1 // alternativt byt while loopen till: // val m = dsp.indexInRange(i, len) // DoubleVector.fromArray(dsp, xs, i, m).mul(DoubleVector.fromArray(dsp, ys, i, m)).intoArray(out, i, m)

Permalänk
Medlem

Bra inlägg från signaturen jclr - håller med om i princip allt!.

Skrivet av jclr:

Dessutom blir koden lättare att läsa och förstå när man väl lärt sig hur de olika algoritmerna fungerar. Det betyder inte att man alltid ska undvika loopar. Ibland kan de behövas för att skriva riktigt optimerad kod och ibland kan en loop faktiskt vara enklare att läsa om den t.ex. bara innehåller en rad kod som ditt exempel. Med optimerad kod menar jag kod som du med en korrekt benchmark (vilket är väldigt komplicerat) kan bevisa faktiskt är snabbare än den kod du redan har (iaf x2 annars är det oftast inte värt det) och som du har ordentligt med tester för. I nästan alla situationer är standard algoritmerna rätt val eller så finns det redan andra optimerade bibliotek som tex för linjär algebra.

Jag vill gå ännu längre än dig: jag efterlyser verkligen exempel där standardalgoritmerna presterar väsentligt sämre än vad en duktig utvecklare kan prestera på en produktiv arbetsdag. Rätt svårt att hitta. Andrei Alexandrescu hade ett föredrag på CppCon där han försöker för bättra std::sort() från algorithm.h. Det krävdes... gaaanska stor effort.
https://www.youtube.com/watch?v=FJJTYQYB1JQ

En av få vettiga anledningar till att använda egna algoritmer är om man jobbar i projekt med höga krav på safety, t.ex. mjukvara i bilar.

Skrivet av jclr:

Inget av det här är nytt när det kommer till programmeringsspråk. Ta reduce t.ex. som kommer från APL. Det är alltså något som funnits i 60 år. Det som är nytt att man måste skriva en massa text istället för bara +/ eller u + v * 3.

Också en bra poäng. Higher-order functions (map / filter / fold) är inte speciellt nytt, utan har funnits sen farfars tid (ingen erfarenhet av APL, men olika Lisp-implementationer hade det här på stenåldern i princip).
https://en.wikipedia.org/wiki/Higher-order_function

Så till ett annat ämne:

Makron är något som absolut inte uppmuntras idag, utan anses vara bad practice (konstiga sidoeffekter, svårt att debugga, osv.).