ノーコード/ローコード環境が利用可能になり、AI がプログラミングに適用されるようになったことで、エンド ユーザーが独自のアプリを作成するか、AI が開発するかのいずれかの理由で、プログラマーの終わりが近づいているという記事が多数投稿されています。説明からアプリを作成します。おそらく…しかしまだです。
この記事では、OpenAI ChatGPTとCodex to Microsoft Copilotの提案を手書きのコードと比較することで、AI 支援コーディングの現状を探ります。 AI は、平均的なコードまたは欠陥のあるコードを生成します。
現在、大量のデータ (ChatGPT と Copilot で使用される大規模な言語モデルを含む) の処理に重点が置かれているため、データを効率的に処理するための鍵となるアルゴリズムに焦点を当てています。うまくいけば、AI は一般的なコードよりも自身の性質を「理解」します!
これらのアルゴリズムは次のとおりです。
交差点
外積
メモ化
ChatGPT にフォームの指示を提供しました。
<関数の説明> を見せて
Codexに入力を提供しました:
<一部機能説明>
WebStorm IDE で Copilot にコメントを提供しました
// <関数の説明>
関数の説明は、それぞれ同じでした。
Jasmine を単体テスト ハーネスとして使用し、 Benchtestにあるものと同様のコードで補足して、速度とメモリと CPU の使用率を取得しました。
可変数の配列を取り、それらの交点を返す JavaScript 関数を示してください。
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 は、1 つ未満の配列を提供することは無効であると想定しました。 1 つの引数を指定することが有効な状況を想像できます。
Setを使用することで、ChatGPT は結果に重複が存在しないことを保証します。交差はセット操作であると想定されており、重複は削除する必要があります。 Copilot コードは、重複を含む可能性のある配列を返します。
ChatGPT も Copilot も元の引数を長さの昇順で並べ替えません。これは些細な最適化であり、大きな違いを生む可能性があります!
長さがゼロの引数がある場合、交差はありません。いずれにせよ、最大交点は最短の配列引数と同じであるため、ループが短縮されます。
Codex が提供する複雑なコードは、私の知る限り非常に効率的でした。ただし、その引数の使用はあいまいで、やや古風なプログラミング スタイルが含まれます。
交差点を呼び出し元のコードに返すには、最後の引数としてコールバックを指定する必要がありますが、引数は示されていません。
次に、ChatGPT と Copilot に効率的な機能を提供するように依頼しました。
可変数の配列を取り、それらの交点を返す効率的な JavaScript 関数を示してください。
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 は、最初の要求と同じコードを生成しました。
ChatGPT はまた、関数がオブジェクトに対して期待どおりに機能しないというコメントも追加しましたが、おそらく機能するでしょう。そこで、オブジェクトをサポートする関数を求めました。
オブジェクトまたはプリミティブを含む可変数の配列を取り、それらの交点を返す効率的な JavaScript 関数を示してください。
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 はオブジェクトを文字列化して比較します。これにはいくつかの問題があります。
同じ文字列表現を持つ 2 つのオブジェクトは、正当に別のオブジェクトである可能性があります。
文字列化が有効な場合でも、プロパティの順序が異なると文字列が異なり、オブジェクトが同じエンティティを表している可能性があります。
JSON.stringifyに続く文字列比較は、処理が遅くなります。
そしていよいよ本番!以下は、ChatGPT、Codex、Copilot、および NPM を介して利用可能な 2 つの最速の交差点ライブラリであるfastArrayIntersectとintersectorによって生成されたコードを使用した速度とメモリのベンチマーク結果です。
ベンチマークは、それぞれ 10,000 個の整数からなる 3 つの同一の配列と、サンプル サイズ 100 の 5,000 個の整数からなる最後の配列を交差させました。ソース配列に重複するエントリが存在する場合に、関数がセットを返すことを確認するテストも実行されました。
ソース | 重複 | 使用ヒープ | 運用部門 | ユーザーCPU | システムCPU |
---|---|---|---|---|---|
チャットGPT | いいえ | 39768 | 6.65 | 152170 | 780 |
コーデックス | いいえ | 5475888 | 16.00 | 69070 | 160 |
副操縦士 | はい | 30768 | 4.16 | 345190 | 940 |
交差点 | いいえ | 37304 | 73.02 | 21020 | 160 |
高速アレイ交差 | いいえ | 42064 | 20.33 | 18990 | 630 |
Copilot はヒープの使用量が最も少ないですが、最も遅く、CPU を最も集中的に使用します。
ChatGPT は CPU 使用率が最も低いですが、低速でもあります。
Fast Array Intersect は、Copilot や Chat GPT の 3 倍から 5 倍、Codex よりも 20% 高速ですが、CPU への負荷は低くなります。
Intersector は、ChatGPT および Copilot よりも 15 倍速く、Codex よりも 4 倍速く、Fast Array Intersect よりも 3 倍以上速く、CPU 使用率も最低です。
要約すれば:
ChatGPT も Copilot も効率的なコードを生成しません。
ChatGPT は、有効ではない可能性がある仮定を行います。
Copilot は、引数に重複する値が含まれている場合にセットを生成しないコードを生成します。
Codex は、あいまいなコードですが、妥当なパフォーマンスを生成します。
プログラマーが勝ちます。
ChatGPT と Copilot はどちらも、その「知性」を利用してユーザーにいくつかの明確な質問をすることで改善できます。
このセクションの目的を明確にするために、ベクトルの算術外積ではなく、配列内の項目の可能なすべての組み合わせを探していました。
デカルト クロス積の JavaScript 関数を見せてください。
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])); }
残念ながら、ChatGPT は算術クロス積の機能を提供していました。私の意見では、両方のタイプの機能を提供するか、説明を求めるべきでした。
さまざまなクエリを複数回試行した後、ChatGPT で以下の 3 つすべてを生成することができました。
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])); }
Intersection と同様に、Codex は、あいまいな引数処理を伴う古いプログラミング スタイルを使用する関数を提供しました。
デカルト積に精通している人は、メモリ使用率とパフォーマンスの両方の観点から、デカルト積が爆発的になる可能性があることを知っているでしょう。
単純な実装は、すべての順列を格納するのに十分な RAM を消費し、すべての順列が生成されるまで結果を返しません (これには長い時間がかかる可能性があります)。 ChatGPT と Copilot 機能には、これらの欠点があります。
それで、私はその後、デカルト積のメモリ効率の良い JavaScript 関数を求めました。
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 は以前と同じ結果を提供しました。
ChatGPT の「メモリ効率の高い」バージョンは、最初のバージョンよりも RAM の使用量が少ないと想定しました。しかし、それが最適ではないことはわかっていました。何度か試みた後、ジェネレーターを使用してデカルト積のメモリ効率の高い JavaScript 関数を思い付きました。
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); }
正直なところ、ChatGPT からのこの回答には驚きました。ただし、 yield [item,...result]
はジェネレーター内ではなく、 recursion
というネストされた関数内にあるため、このコードを実行しようとすると失敗しました。
また、クエリを失ったわずかなバリエーションも受け取りました。
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]]); }
繰り返しになりますが、ChatGPT は引数についていくつかの推測を行っているようです。
Copilot は以前と同じコードを返しました。ジェネレーターに対する私の要求を完全に無視しました。
そしていよいよ本番!以下は、ChatGPT、Copilot、およびCXProductとLazy Cartesian Productを認識している最速で最もメモリ効率の高いデカルト積によって生成されたコードを使用した速度とメモリのベンチマーク結果です。
また、ChatGPT または Copilot のいずれかが返すはずだった 1 行の関数も含まれています (結果を確認すると、これを含めた理由がわかります)。
//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())));
このテストでは、それぞれ 10 個の項目を持つ 4 つの配列のデカルト積が使用されます。したがって、関数は 10 * 10 * 10 * 10 アイテム、つまり 10,000 のデカルト積を生成する必要があります。
ソース | # 結果 | 使用ヒープ | 運用部門 | ユーザーCPU | システムCPU |
---|---|---|---|---|---|
チャットGPT1 | 300 | なし | なし | なし | なし |
ChatGPT2 | 50 | なし | なし | なし | なし |
ChatGPT3 | 600 | なし | なし | なし | なし |
ChatGPT メモリ効率が良い | なし | なし | なし | なし | なし |
ChatGPT ジェネレーター | 1000 | なし | なし | なし | なし |
コーデックス | 4 | なし | なし | なし | なし |
副操縦士 | 30 | なし | なし | なし | なし |
一行 | 10000 | 0 | 78.46 | 18150 | 0 |
CX製品 | 10000 | 0 | 291.50 | 7330 | 0 |
レイジープロダクト | 10000 | 17824 | 266.65 | 11560 | 150 |
要約すれば:
ChatGPT、Codex、および Copilot は、デカルト積の正しい結果を生成するコードを生成できません。
ChatGPT は、たとえば 2 つの引数を必要とするなど、有効でない可能性のある仮定を行うことがあります。
ChatGPT は、よりメモリ効率が良いと言ったコードを生成しましたが、実行さえしませんでした。
プログラマーの勝ち!
繰り返しになりますが、ChatGPT は、その「知性」を利用してユーザーにいくつかの明確な質問をすることで改善できます。ただし、その前に、正しいコードを生成する必要があります。
交点とデカルト積の教訓を学んだので、すぐにメモ化のための洗練された質問をしました。
オブジェクトとプリミティブ引数を記憶する効率的な JavaScript 関数を示してください。
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 が無効なコードを生成しました。 WeakMap
はオブジェクトをキーとして使用する必要があります。テスト用にコードを次のように変更しました。
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 と Copilot は同じ関数を生成しましたが、Copilot が Codex に基づいていることを考えると、驚くべきことではありません。ただし、前の例では、それらは異なっていました。
ChatGPT、Codex、および Copilot によって生成された非効率的な関数JSON.stringify に続く文字列比較は、規模が大きくなると遅くなり、メモリを消費します。
Infinity や NaN など、文字列化されない JavaScript 値もあります。 (残念ながら、JavaScript の JSON 仕様は、データ サイエンスとマイクロ サービスの時代より前に定義されたものであり、Infinity と NaN は、正当でないか、ある場所から別の場所に転送する必要のないコードのエラーを暗示していると想定されていました。 .)
それでは、次のコードを使用して 12 番目のフィボナッチ数を生成することにより、ChatGPT および Copilot コードをnano-memoizeおよびmicro- memoize と比較して効率性を証明します。
const fibonacci = (number) => { return number < 2 ? number : fibonacci(number - 1) + fibonacci(number - 2); };
ソース | 使用ヒープ | 運用部門 | ユーザーCPU | システムCPU |
---|---|---|---|---|
ChatGPT (修正済み) | 102552 | 45801 | 620 | 0 |
コーデックス | 17888 | 52238 | 320 | 0 |
副操縦士 | 17888 | 51301 | 320 | 0 |
ナノメモイズ | 17576 | 93699 | 470 | 160 |
マイクロメモ化 | 18872 | 82833 | 620 | 0 |
Nano-memoize は最も高速で、ChatGPT、Codex、および Copilot コードのほぼ 2 倍の速さです。また、メモリ使用量も少なくなります。 Micro-memoize はそのすぐ後にあります。
nano-memoize
とmicro-memoize
の CPU 使用率は Code と Copilot よりもわずかに高くなりますが、パフォーマンスはそれだけの価値があり、プログラマーが再び勝ちます!
コード生成に Copilot と ChatGPT の両方を使用することには確かに価値がありますが、慎重に行う必要があります。どちらも最適なコードを生成するわけではなく、場合によっては、単に無効であるか、さらに悪いことに、正しくないものになります。さらに、ChatGPT を使用する場合、クエリは非常に具体的である必要があります。
ChatGPT と Copilot は、明確な質問をする機能を追加することで改善される可能性があります。
ChatGPT が本当にインテリジェントな場合、コード生成に兄弟の Codex を使用するようユーザーに指示するか、単純に Codex をバックグラウンドで使用します。
バックグラウンドで Codex を使用している場合、同じ関数の説明を両方に提供して異なる結果を得たときに何が起こっているのかわかりません。
私はどちらのツールの内部動作にも精通していませんが、それらが言語モデル ベースであることを除けば、この欠点を克服せずに最適なコードを生成できるようになる可能性は低いと推測しています。
公に検証されていない大量のコードでトレーニングされたシステムは、コードの平均的な結果、つまり平均的なパフォーマンスのコードと平均的なバグ数のコードを生成します。
一貫して正確な結果を提供するには、システムに次のものが必要です。
JSON.stringify などの「カウンター サンプル」データの断片を消費して使用する機能は、非効率的である可能性があります。システムは、コードと同様にテスト結果を分析するか、何らかの重みを付けて既知の最適なコードを与えられるか、単に既知の専門家による結果の批評を通じて、この機能を得ることができます。残念ながら、最適なコードが最も普及していないか、最も使用されているわけではないことが多く、単純にモデルにより多くの例を与えるだけでは役に立ちません。理想的なケースでは、本当にインテリジェントなシステムが独自のテスト ケースを生成できます。
効率的な欠陥について生成するコードを分析するための、プログラミングのより深い、より第一の原則の「理解」。たとえば、通常、実行時の効率のために再帰よりも反復を優先し、通常はコードサイズと可読性のために再帰を優先します。
少なくとも、コード生成 AI は、生成したコードを解析し、その構文の妥当性を評価しようとする必要があります。これは、ChatGPT の単純な機能強化です。
理想的には、AI は少なくとも 1 つまたは 2 つの単純なテスト ケースも実行して、型の妥当性を確認します。私が単体テストを作成しているときに、Copilot は、関数呼び出しの引数や配列ルックアップなど、この目的に使用できるいくつかの便利な拡張コード補完を提案しました。 ChatGPT と Codex を拡張して、そのようなことを行うことができると思います。
この記事をお楽しみいただけたでしょうか。素晴らしい一日をお過ごしください。昨日は、今日学んだこと (ここまたは他の場所で) を嫉妬させてください!
こちらにも掲載