Angesichts der zunehmenden Verfügbarkeit von No-Code-/Low-Code-Umgebungen und dem Aufkommen der Anwendung von KI in der Programmierung wurde in einer Reihe von Artikeln berichtet, dass das Ende für Programmierer nahe sei, entweder weil Endbenutzer ihre eigenen Apps erstellen oder die KI dies tun werde Schreiben Sie Apps aus Beschreibungen. Vielleicht … aber noch nicht.
In diesem Artikel untersuche ich den aktuellen Stand der KI-gestützten Codierung, indem ich die Vorschläge von OpenAI ChatGPT und Codex to Microsoft Copilot mit handgeschriebenem Code vergleiche. Die KIs produzieren entweder durchschnittlichen oder fehlerhaften Code.
Da heutzutage so viel Wert auf die Verarbeitung großer Datenmengen gelegt wird (einschließlich der großen Sprachmodelle, die für ChatGPT und Copilot verwendet werden), konzentriere ich mich auf Algorithmen, die für eine effiziente Datenverarbeitung von entscheidender Bedeutung sind. Hoffentlich „verstehen“ die KIs ihre eigene Natur besser als Code im Allgemeinen!
Diese Algorithmen sind:
Überschneidung
Kreuzprodukt
auswendig lernen
Ich habe ChatGPT Anweisungen für das Formular bereitgestellt:
Zeigen Sie mir <einige Funktionsbeschreibung>
Ich habe Codex mit Eingaben versorgt:
<einige Funktionsbeschreibung>
Ich habe Copilot Kommentare in einer WebStorm-IDE bereitgestellt
// <einige Funktionsbeschreibung>
Die Funktionsbeschreibungen waren jeweils gleich.
Ich habe Jasmine als Unit-Test-Harnisch verwendet, ergänzt durch Code, der dem in Benchtest ähnelt, um Geschwindigkeit sowie Speicher- und CPU-Auslastung zu erzielen.
Zeigen Sie mir eine JavaScript-Funktion, die eine variable Anzahl von Arrays akzeptiert und deren Schnittmenge zurückgibt.
function intersection(...arrays) { if (arrays.length < 2) { throw new Error("At least 2 arrays are required!"); } let result = new Set(arrays.shift()); for (let arr of arrays) { result = new Set([...result].filter(x => arr.includes(x))); } return [...result]; }
function intersection(arrays) { var result = []; var args = Array.prototype.slice.call(arguments); var arrays = args.slice(0, args.length - 1); var callback = args[args.length - 1]; var minLength = Math.min.apply(null, arrays.map(function(arr) { return arr.length; })); for (var i = 0; i < minLength; i++) { var item = arrays[0][i]; var isInAll = arrays.every(function(arr) { return arr.indexOf(item) !== -1; }); if (isInAll) { result.push(item); } } return callback(result); }
const intersection = (...arrays) => { const [first, ...rest] = arrays; return first.filter(x => rest.every(y => y.includes(x))); }
ChatGPT ging davon aus, dass die Bereitstellung von weniger als einem Array ungültig ist. Ich kann mir Situationen vorstellen, in denen die Angabe eines Arguments gültig sein könnte.
Durch die Verwendung eines Set stellt ChatGPT sicher, dass in den Ergebnissen keine Duplikate vorhanden sind. Der Schnittpunkt soll eine festgelegte Operation sein und Duplikate sollten entfernt werden. Der Copilot-Code gibt ein Array zurück, das möglicherweise Duplikate enthält.
Weder ChatGPT noch Copilot sortieren die Originalargumente aufsteigend nach Länge. Dies ist eine triviale Optimierung, die einen großen Unterschied machen könnte!
Wenn ein Argument die Länge Null hat, gibt es keinen Schnittpunkt; Unabhängig davon werden die Schleifen verkürzt, da der maximale Schnittpunkt mit dem kürzesten Array-Argument übereinstimmt.
Codex stellte komplexen Code bereit, der meines Wissens nach recht effizient aussah. Die Verwendung von Argumenten ist jedoch unklar und weist einen etwas archaischen Programmierstil auf.
Beachten Sie, dass als letztes Argument ein Rückruf angegeben werden muss, um die Schnittmenge an den aufrufenden Code zurückzugeben, das Argument wird jedoch nicht angezeigt.
Ich habe dann ChatGPT und Copilot gebeten, mir eine effiziente Funktion zur Verfügung zu stellen.
Zeigen Sie mir eine effiziente JavaScript-Funktion, die eine variable Anzahl von Arrays verwendet und deren Schnittmenge zurückgibt.
function intersection(...arrays) { if (arrays.length < 2) { throw new Error("At least 2 arrays are required!"); } let result = new Set(); for (let item of arrays[0]) { let include = true; for (let i = 1; i < arrays.length; i++) { if(!arrays[i].includes(item)){ include = false; break; } } if(include){ result.add(item); } } return [...result]; }
Copilot hat denselben Code wie bei der ersten Anfrage generiert.
ChatGPT fügte außerdem einen Kommentar hinzu, der besagte, dass die Funktion für Objekte nicht wie erwartet funktionieren würde, obwohl dies wohl der Fall sein würde. Also habe ich nach einer Funktion gefragt, die Objekte unterstützen würde:
Zeigen Sie mir eine effiziente JavaScript-Funktion, die eine variable Anzahl von Arrays mit Objekten oder Grundelementen verwendet und deren Schnittmenge zurückgibt.
function intersection(...arrays) { if (arrays.length < 2) { throw new Error("At least 2 arrays are required!"); } let result = new Set(); for (let item of arrays[0]) { let include = true; for (let i = 1; i < arrays.length; i++) { if(!arrays[i].find(x => JSON.stringify(x) === JSON.stringify(item))){ include = false; break; } } if(include){ result.add(item); } } return [...result]; } This function takes an arbitrary number of arrays as arguments and first checks if at least 2 arrays are passed. Then it uses a nested loop. The outer loop iterates over the first array and the inner loop iterates over the remaining arrays. It compares each item of the first array with each item of the remaining arrays using Array.prototype.find() method and JSON.stringify() to check if the objects are the same. If an item is found in all arrays, it is added to
ChatGPT sortiert Objekte in Strings, um sie zu vergleichen. Dabei gibt es eine Reihe von Problemen:
Zwei Objekte, die dieselbe Zeichenfolgendarstellung haben, können legitimerweise separate Objekte sein.
Selbst wenn die Zeichenfolgenbildung gültig ist, führen unterschiedliche Eigenschaftsreihenfolgen zu unterschiedlichen Zeichenfolgen, und die Objekte repräsentieren möglicherweise dieselbe Entität.
JSON.stringify gefolgt von einem String-Vergleich ist ein langsamer Vorgang.
Und jetzt der eigentliche Test! Nachfolgend finden Sie Benchmark-Ergebnisse für Geschwindigkeit und Speicher unter Verwendung von Code, der von ChatGPT, Codex, Copilot und den beiden schnellsten über NPM verfügbaren Schnittpunktbibliotheken, fastArrayIntersect und intersector , generiert wurde.
Der Benchmark schnitt drei identische Arrays mit jeweils 10.000 Ganzzahlen und ein abschließendes Array mit 5.000 Ganzzahlen mit einer Stichprobengröße von 100. Außerdem wurde ein Test durchgeführt, um zu bestätigen, dass die Funktionen Sätze zurückgaben, wenn in den Quellarrays doppelte Einträge vorhanden waren.
Quelle | Duplikate | Heap verwendet | Ops Sec | Benutzer-CPU | System-CPU |
---|---|---|---|---|---|
ChatGPT | NEIN | 39768 | 6,65 | 152170 | 780 |
Kodex | NEIN | 5475888 | 16.00 | 69070 | 160 |
Kopilot | Ja | 30768 | 4.16 | 345190 | 940 |
Schnittmenge | NEIN | 37304 | 73.02 | 21020 | 160 |
Schnelle Array-Kreuzung | NEIN | 42064 | 20.33 | 18990 | 630 |
Copilot nutzt den geringsten Heap, ist aber auch am langsamsten und rechenintensivsten.
ChatGPT ist am wenigsten CPU-intensiv, aber auch langsam.
Fast Array Intersect ist drei- bis fünfmal so schnell wie Copilot und Chat GPT und 20 % schneller als Codex und gleichzeitig weniger CPU-intensiv.
Intersector ist 15-mal schneller als ChatGPT und Copilot, 4-mal schneller als Codex und mehr als dreimal schneller als Fast Array Intersect und weist gleichzeitig die niedrigste CPU-Auslastung auf.
In Summe:
Weder ChatGPT noch Copilot generieren effizienten Code.
ChatGPT geht von Annahmen aus, die möglicherweise nicht gültig sind.
Copilot generiert Code, der keine Menge erzeugt, wenn seine Argumente doppelte Werte enthalten.
Codex erzeugt einigermaßen leistungsfähigen, wenn auch undurchsichtigen Code.
Die Programmierer gewinnen.
Sowohl ChatGPT als auch Copilot könnten verbessert werden, indem sie ihre „Intelligenz“ einsetzen, um dem Benutzer ein paar klärende Fragen zu stellen, z. B. suchen Sie nach Geschwindigkeit oder Speichereffizienz, möchten Sie mindestens zwei Argumente benötigen?
Um das Ziel dieses Abschnitts klarzustellen: Ich habe nach allen möglichen Kombinationen von Elementen in Arrays gesucht, nicht nach einem arithmetischen Kreuzprodukt von Vektoren.
Zeigen Sie mir eine JavaScript-Funktion für kartesisches Kreuzprodukt.
function cartesianCrossProduct(v1, v2) { var x = v1.y*v2.z - v1.z*v2.y; var y = v1.z*v2.x - v1.x*v2.z; var z = v1.x*v2.y - v1.y*v2.x; return {x: x, y: y, z: z}; }
function crossProduct(arrays) { var result = []; var args = Array.prototype.slice.call(arguments); var arrays = args.slice(0, args.length - 1); var callback = args[args.length - 1]; var maxLength = Math.max.apply(null, arrays.map(function(arr) { return arr.length; })); for (var i = 0; i < maxLength; i++) { var item = arrays[0][i]; var isInAll = arrays.every(function(arr) { return arr.indexOf(item) !== -1; }); if (isInAll) { result.push(item); } } return callback(result); }
const crossProduct = (...arrays) => { const [first, ...rest] = arrays; return first.flatMap(x => rest.map(y => [x, y])); }
Leider bietet ChatGPT eine Funktion zur arithmetischen Kreuzproduktberechnung. Meiner Meinung nach hätte es beide Arten von Funktionen bereitstellen oder um Klarstellung bitten sollen.
Nach mehreren Versuchen mit unterschiedlichen Abfragen konnte ich ChatGPT dazu bringen, alle drei der folgenden Punkte zu generieren:
function crossProduct(...arrays) { if (arrays.length < 2) { throw new Error("At least 2 arrays are required!"); } let result = arrays[0].reduce((acc, x) => { return acc.concat(arrays.slice(1).reduce((acc, y) => { return acc.concat(y.map(z => [x].concat(z))); }, [])) }, []); return result; }
function allCombinations(...args) { var result = []; var f = function(prefix, arr) { for (var i = 0; i < arr.length; i++) { var newPrefix = prefix.concat(arr[i]); result.push(newPrefix); if (args.length > 0) { f(newPrefix, args.shift()); } } } f([], args[0]); return result; }
function crossProduct(...args) { var result = []; for(let i = 0; i < args.length; i++){ for(let j = i + 1; j < args.length; j++){ for(let ii = 0; ii < args[i].length; ii++){ for(let jj = 0; jj < args[j].length; jj++){ result.push([args[i][ii], args[j][jj]]); } } } } return result; }
const crossProduct = (...arrays) => { const [first, ...rest] = arrays; return first.flatMap(x => rest.map(y => [x, y])); }
Wie bei der Schnittmenge stellte Codex eine Funktion bereit, die einen alten Programmierstil mit unklarer Argumentbehandlung nutzte.
Wer mit kartesischen Produkten vertraut ist, weiß, dass diese sowohl im Hinblick auf die Speicherauslastung als auch auf die Leistung explosiv sein können.
Naive Implementierungen verbrauchen ausreichend RAM, um alle Permutationen zu speichern, und geben kein Ergebnis zurück, bis alle Permutationen generiert wurden (was lange dauern kann). Die Funktionen ChatGPT und Copilot leiden beide unter diesen Mängeln.
Daher habe ich anschließend nach einer speichereffizienten JavaScript-Funktion für das kartesische Produkt gefragt.
function cartesianProduct(...args) { const iterators = args.map(arg => arg[Symbol.iterator]()); const next = () => { const values = iterators.map(iterator => iterator.next().value); return values.length > 1 ? values : null; }; return { [Symbol.iterator]: () => ({ next }) }; }
Copilot lieferte die gleichen Ergebnisse wie zuvor.
Ich ging davon aus, dass die „speichereffiziente“ Version von ChatGPT weniger RAM verbrauchte als die erste. Ich wusste jedoch, dass es nicht optimal war. Nach mehreren Versuchen habe ich mithilfe eines Generators eine speichereffiziente JavaScript-Funktion für kartesische Produkte entwickelt.
function* cartesianProduct(...args) { const recursion = (arrays, i = 0) => { if (i === arrays.length - 1) { yield* arrays[i]; } else { for (let item of arrays[i]) { for (let result of recursion(arrays, i + 1)) { yield [item, ...result]; } } } } yield* recursion(args); }
Ehrlich gesagt war ich von dieser Antwort von ChatGPT angenehm überrascht. Versuche, diesen Code auszuführen, schlugen jedoch fehl, da yield [item,...result]
sich nicht in einem Generator, sondern in einer verschachtelten Funktion namens recursion
befindet.
Ich habe auch eine leichte Variation erhalten, bei der ich die Abfrage verloren habe:
function* crossProduct(...arrays) { if (arrays.length < 2) { throw new Error("At least 2 arrays are required!"); } function* recurse(depth, current) { if (depth === arrays.length) { yield current; } else { for (let item of arrays[depth]) { yield* recurse(depth + 1, current.concat(item)); } } } yield* recurse(1, [arrays[0]]); }
Wieder einmal schien ChatGPT einige Annahmen über Argumente zu treffen.
Copilot hat den gleichen Code zurückgegeben wie zuvor. Meine Bitte um einen Generator wurde völlig ignoriert.
Und jetzt der eigentliche Test! Nachfolgend finden Sie Benchmark-Ergebnisse für Geschwindigkeit und Speicher unter Verwendung von Code, der von ChatGPT, Copilot und den schnellsten und speichereffizientesten kartesischen Produkten generiert wurde, die mir bekannt sind : CXProduct und Lazy Cartesian Product .
Außerdem ist eine einzeilige Funktion enthalten, die möglicherweise entweder ChatGPT oder Copilot hätte zurückgeben sollen (Sie werden sehen, warum ich diese eingefügt habe, wenn Sie die Ergebnisse überprüfen).
//https://stackoverflow.com/questions/12303989/cartesian-product-of-multiple-arrays-in-javascript const cartesian = (...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));
Der Test verwendet das kartesische Produkt von 4 Arrays mit jeweils 10 Elementen. Daher sollten die Funktionen ein kartesisches Produkt von 10 * 10 * 10 * 10 Elementen erzeugen, also 10.000.
Quelle | # Ergebnisse | Heap verwendet | Ops Sec | Benutzer-CPU | System-CPU |
---|---|---|---|---|---|
ChatGPT1 | 300 | N / A | N / A | N / A | N / A |
ChatGPT2 | 50 | N / A | N / A | N / A | N / A |
ChatGPT3 | 600 | N / A | N / A | N / A | N / A |
ChatGPT speichereffizient | N / A | N / A | N / A | N / A | N / A |
ChatGPT-Generator | 1000 | N / A | N / A | N / A | N / A |
Kodex | 4 | N / A | N / A | N / A | N / A |
Kopilot | 30 | N / A | N / A | N / A | N / A |
Eine Linie | 10000 | 0 | 78,46 | 18150 | 0 |
CXProdukt | 10000 | 0 | 291,50 | 7330 | 0 |
LazyProduct | 10000 | 17824 | 266,65 | 11560 | 150 |
In Summe:
ChatGPT, Codex und Copilot sind nicht in der Lage, Code zu generieren, der das richtige Ergebnis für ein kartesisches Produkt liefert.
ChatGPT geht manchmal von Annahmen aus, die möglicherweise nicht gültig sind, z. B. erfordert es zwei Argumente.
ChatGPT generierte Code, der angeblich speichereffizienter war, aber nicht einmal ausgeführt werden konnte.
Die Programmierer gewinnen!
Auch hier könnte ChatGPT verbessert werden, indem seine „Intelligenz“ darauf angewendet wird, dem Benutzer ein paar klärende Fragen zu stellen, z. B.: Suchen Sie nach Geschwindigkeit oder Speichereffizienz, möchten Sie mindestens zwei Argumente benötigen? Zuvor muss jedoch der richtige Code generiert werden!
Nachdem ich meine Lektion über Schnittmengen und kartesische Produkte gelernt hatte, stellte ich sofort eine verfeinerte Frage zum Auswendiglernen.
Zeigen Sie mir eine effiziente JavaScript-Funktion, die Objekt- und Grundargumente speichert.
function memoize(fn) { let cache = new WeakMap(); return function(obj, ...args) { let key; if (obj) { if (!cache.has(obj)) { cache.set(obj, {}); } key = JSON.stringify([obj, ...args]); } else { key = JSON.stringify(args); } if (cache.has(key)) { return cache.get(key); } let result = fn.apply(this, [obj, ...args]); cache.set(key, result); return result; } }
ChatCPT hat ungültigen Code generiert; Eine WeakMap
muss ein Objekt als Schlüssel verwenden. Der Code wurde zum Testen wie folgt geändert.
function memoize(fn) { let cache = new Map(); return function(obj, ...args) { let key; if (obj!=null) { if (!cache.has(obj)) { cache.set(obj, {}); } key = JSON.stringify([obj, ...args]); } else { key = JSON.stringify(args); } if (cache.has(key)) { return cache.get(key); } let result = fn.apply(this, [obj, ...args]); cache.set(key, result); return result; } }
function memoize(func) { var cache = {}; return function() { var args = Array.prototype.slice.call(arguments); var key = JSON.stringify(args); if (cache[key]) { return cache[key]; } else { var result = func.apply(null, args); cache[key] = result; return result; } } }
const memoize = (fn, cache = new Map()) => { return (...args) => { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } else { const result = fn(...args); cache.set(key, result); return result; } } }
Codex und Copilot haben die gleiche Funktion generiert, was angesichts der Tatsache, dass Copilot auf Codex basiert, nicht überraschend sein sollte. Obwohl sie in den früheren Beispielen unterschiedlich waren.
Die von ChatGPT, Codex und Copilot generierten ineffizienten Funktionen JSON.stringify, gefolgt von einem String-Vergleich, sind langsam und verbrauchen im großen Maßstab Speicher.
Es gibt auch JavaScript-Werte, die nicht stringifiziert sind, z. B. Infinity und NaN. (Leider wurde die JavaScript-JSON-Spezifikation vor dem Zeitalter der Datenwissenschaft und Mikrodienste definiert und man ging davon aus, dass Infinity und NaN Fehler im Code implizierten, die entweder nicht legitim waren oder nicht von einem Ort zum anderen transportiert werden mussten .)
Nun zum Beweis der Effizienz durch den Vergleich von ChatGPT- und Copilot-Code mit Nano-Memoize und Micro-Memoize durch Generierung der 12. Fibonacci-Zahl mit diesem Code:
const fibonacci = (number) => { return number < 2 ? number : fibonacci(number - 1) + fibonacci(number - 2); };
Quelle | Heap verwendet | Ops Sec | Benutzer-CPU | System-CPU |
---|---|---|---|---|
ChatGPT (korrigiert) | 102552 | 45801 | 620 | 0 |
Kodex | 17888 | 52238 | 320 | 0 |
Kopilot | 17888 | 51301 | 320 | 0 |
Nano-Memoize | 17576 | 93699 | 470 | 160 |
Mikro-Auswendiglernen | 18872 | 82833 | 620 | 0 |
Nano-Memoize ist am schnellsten und fast doppelt so schnell wie ChatGPT-, Codex- und Copilot-Code. Es verbraucht auch weniger Speicher. Micro-Memoize ist dicht auf den Fersen.
Obwohl die CPU-Auslastung für nano-memoize
und micro-memoize
etwas höher ist als bei Code und Copilot, lohnt sich die Leistung und die Programmierer gewinnen wieder einmal!
Obwohl die Verwendung von Copilot und ChatGPT für die Codegenerierung sicherlich von Nutzen ist, sollte dies mit Vorsicht erfolgen. Beides führt nicht zu optimalem Code und in manchen Fällen ist er einfach ungültig oder, schlimmer noch, falsch. Darüber hinaus sollten bei der Verwendung von ChatGPT die Abfragen recht spezifisch sein.
Sowohl ChatGPT als auch Copilot könnten durch die Hinzufügung einer Funktion verbessert werden, die klärende Fragen stellt.
Wenn ChatGPT wirklich intelligent wäre, würde es die Benutzer entweder auffordern, seinen Schwestercodex für die Codegenerierung zu verwenden, oder Codex einfach im Hintergrund verwenden.
Wenn Codex im Hintergrund verwendet wird, bin ich mir nicht sicher, was passiert, wenn ich beiden dieselbe Funktionsbeschreibung gebe und unterschiedliche Ergebnisse erhalte.
Obwohl ich mit der internen Funktionsweise beider Tools nicht vertraut bin, abgesehen von dem Wissen, dass sie auf Sprachmodellen basieren, gehe ich davon aus, dass es unwahrscheinlich ist, dass sie an einen Punkt gelangen, an dem sie optimalen Code generieren können, ohne diesen Mangel zu beheben:
Ein System, das auf große Mengen öffentlich ungeprüften Codes trainiert wird, wird durchschnittliche Ergebnisse für Code liefern, dh Code mit durchschnittlicher Leistung und Code mit einer durchschnittlichen Anzahl von Fehlern.
Um stets genaue Ergebnisse zu liefern, benötigt das System Folgendes:
Die Möglichkeit, Fragmente von „Gegenbeispiel“-Daten, z. B. JSON.stringify, zu konsumieren und zu verwenden, kann ineffizient sein. Das System könnte diese Fähigkeit erlangen, indem es Testergebnisse und Code analysiert oder mit bekanntem optimalem Code mit einer Gewichtung gefüttert wird oder einfach durch die Kritik der Ergebnisse durch bekannte Experten. Leider ist optimaler Code oft nicht der am weitesten verbreitete oder am häufigsten verwendete Code, und es hilft nicht, den Modellen einfach weitere Beispiele zuzuführen. Im Idealfall wäre ein wirklich intelligentes System in der Lage, eigene Testfälle zu generieren.
Ein tieferes, grundlegenderes „Verständnis“ der Programmierung, um den von ihm generierten Code auf Effizienzmängel zu analysieren, z. B. bevorzugen normalerweise Iteration gegenüber Rekursion für Laufzeiteffizienz, bevorzugen normalerweise Rekursion für Codegröße und Lesbarkeit.
Zumindest sollte eine codegenerierende KI versuchen, den von ihr generierten Code zu analysieren und seine syntaktische Gültigkeit zu bewerten. Dies sollte eine einfache Erweiterung von ChatGPT sein.
Idealerweise würde die KI auch mindestens einen oder zwei einfache Testfälle ausführen, um die Typgültigkeit sicherzustellen. Während ich Unit-Tests erstellte, schlug Copilot eine Reihe nützlicher erweiterter Code-Vervollständigungen vor, die für diesen Zweck verwendet werden könnten, z. B. Argumente für Funktionsaufrufe und Array-Suchen. Ich gehe davon aus, dass ChatGPT und Codex für so etwas erweitert werden könnten.
Ich hoffe, Ihnen hat dieser Artikel gefallen. Ich wünsche Ihnen einen schönen Tag und machen Sie den gestrigen Tag neidisch auf das, was Sie heute (hier oder anderswo) gelernt haben!
Auch hier veröffentlicht