노코드/로우코드 환경의 가용성이 높아지고 프로그래밍에 AI가 적용되면서 최종 사용자가 자신의 앱을 만들거나 AI가 앱을 만들 것이기 때문에 프로그래머의 종말이 가까워졌다는 기사가 많이 게시되었습니다. 설명에서 앱을 작성하세요. 아마도… 하지만 아직은 아닙니다.
이 기사에서는 OpenAI ChatGPT 및 Codex 의 제안과 Microsoft Copilot 의 제안을 직접 작성한 코드와 비교하여 AI 지원 코딩의 현재 상태를 살펴봅니다. AI는 평균적이거나 결함이 있는 코드를 생성합니다.
오늘날에는 대량의 데이터(ChatGPT 및 Copilot에 사용되는 대규모 언어 모델 포함)를 처리하는 데 많은 초점이 맞춰져 있으므로 데이터를 효율적으로 처리하는 데 핵심이 되는 알고리즘에 중점을 둡니다. AI가 일반적인 코드보다 자신의 특성을 더 잘 "이해"하기를 바랍니다!
이러한 알고리즘은 다음과 같습니다.
교차로
교차곱
메모하기
나는 ChatGPT에 다음 양식에 대한 지침을 제공했습니다.
<일부 기능 설명> 보여주세요
나는 Codex에 다음과 같은 입력을 제공했습니다.
<일부 기능 설명>
WebStorm IDE에서 Copilot에 주석을 제공했습니다.
// <일부 함수 설명>
기능 설명은 각각 동일했습니다.
나는 속도와 메모리 및 CPU 활용도를 얻기 위해 Benchtest 에서 발견된 것과 유사한 코드로 보완된 단위 테스트 도구로 Jasmine을 사용했습니다.
가변 개수의 배열을 취하고 그 교차점을 반환하는 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는 하나 미만의 배열을 제공하는 것이 유효하지 않다고 가정했습니다. 하나의 주장을 제시하는 것이 타당할 수 있는 상황을 상상할 수 있습니다.
Set 을 사용하여 ChatGPT는 결과에 중복이 없는지 확인합니다. 교차점은 집합 연산으로 간주되며 중복된 항목은 제거되어야 합니다. Copilot 코드는 중복을 포함할 수 있는 배열을 반환합니다.
ChatGPT나 Copilot은 원래 인수를 길이순으로 오름차순으로 정렬하지 않습니다. 이것은 큰 변화를 가져올 수 있는 사소한 최적화입니다!
길이가 0인 인수가 있으면 교차점이 없습니다. 그럼에도 불구하고 최대 교차점이 가장 짧은 배열 인수와 동일하기 때문에 루프가 단축됩니다.
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는 객체를 문자열화하여 비교합니다. 여기에는 여러 가지 문제가 있습니다.
동일한 문자열 표현을 가진 두 개체는 합법적으로 별도의 개체일 수 있습니다.
문자열화가 유효하더라도 속성 순서가 다르면 문자열도 달라지며 개체는 동일한 엔터티를 나타낼 수도 있습니다.
JSON.stringify 와 문자열 비교는 느린 작업입니다.
그리고 이제 진짜 테스트! 다음은 ChatGPT, Codex, Copilot에서 생성된 코드와 NPM, 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에서 아래 세 가지를 모두 생성할 수 있었습니다.
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 중 가장 빠르고 메모리 효율적인 Cartesian 제품에서 생성된 코드를 사용한 속도 및 메모리에 대한 벤치마크 결과입니다.
또한 ChatGPT 또는 Copilot이 반환했어야 할 한 줄 함수도 포함되어 있습니다. 결과를 검토하면 이 기능을 포함시킨 이유를 알 수 있습니다.
//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 | 해당 없음 | 해당 없음 | 해당 없음 | 해당 없음 |
채팅GPT2 | 50 | 해당 없음 | 해당 없음 | 해당 없음 | 해당 없음 |
채팅GPT3 | 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 코드를 나노 메모 및 마이크로 메모 와 비교하여 효율성을 증명합니다.
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 코드보다 가장 빠르고 거의 두 배 빠릅니다. 또한 메모리를 덜 사용합니다. Micro-memoize가 바로 그 발뒤꿈치에 있습니다.
nano-memoize
및 micro-memoize
의 CPU 사용률은 Code 및 Copilot보다 약간 높지만 성능은 그만한 가치가 있으며 프로그래머가 다시 한 번 승리합니다!
코드 생성을 위해 Copilot과 ChatGPT를 모두 사용하면 확실히 가치가 있지만 주의해서 사용해야 합니다. 둘 다 최적의 코드를 생성하지 않으며 어떤 경우에는 단순히 유효하지 않거나 더 나쁘거나 부정확할 수도 있습니다. 또한 ChatGPT를 사용할 때 쿼리는 매우 구체적이어야 합니다.
ChatGPT와 Copilot은 명확한 질문을 하는 기능을 추가하여 개선될 수 있습니다.
ChatGPT가 정말로 지능적이라면 사용자에게 코드 생성을 위해 형제 Codex를 사용하라고 지시하거나 단순히 백그라운드에서 Codex를 사용할 것입니다.
백그라운드에서 Codex를 사용하는 경우 두 가지 모두에 동일한 기능 설명을 제공하고 다른 결과를 얻을 때 무슨 일이 일어나고 있는지 잘 모르겠습니다.
두 도구의 내부 작동 방식에 익숙하지 않지만 언어 모델 기반이라는 점 외에는 이러한 단점을 극복하지 않고 최적의 코드를 생성할 수 있는 지점에 도달할 가능성은 거의 없다고 가정합니다.
공개적으로 검증되지 않은 대량의 코드에 대해 훈련된 시스템은 코드에 대한 평균 결과, 즉 평균 성능의 코드와 평균 버그 수가 있는 코드를 생성할 것입니다.
지속적으로 정확한 결과를 제공하려면 시스템에 다음이 필요합니다.
JSON.stringify 와 같은 "카운터 샘플" 데이터의 조각을 소비하고 사용하는 기능은 비효율적일 수 있습니다. 시스템은 테스트 결과와 코드를 분석하거나 특정 유형의 가중치를 적용한 알려진 최적의 코드를 제공하거나 단순히 알려진 전문가의 결과 비평을 통해 이 기능을 얻을 수 있습니다. 불행하게도 최적의 코드는 가장 일반적이거나 가장 많이 사용되지 않는 경우가 많으며 단순히 모델에 더 많은 예제를 제공하는 것만으로는 도움이 되지 않습니다. 이상적인 경우에는 정말 지능적인 시스템이 자체 테스트 사례를 생성할 수 있습니다.
효율성 결함을 위해 생성된 코드를 분석하기 위해 프로그래밍에 대한 더 깊고 첫 번째 원칙 "이해"는 일반적으로 런타임 효율성을 위해 재귀보다 반복을 선호하고 일반적으로 코드 크기 및 가독성을 위해 재귀를 선호합니다.
최소한 코드 생성 AI는 자신이 생성한 코드를 구문 분석하고 구문 유효성을 평가해야 합니다. 이는 ChatGPT를 간단하게 개선한 것입니다.
이상적으로 AI는 유형 유효성을 확인하기 위해 최소한 하나 또는 두 개의 간단한 테스트 사례를 실행합니다. 단위 테스트를 작성하는 동안 Copilot은 함수 호출 및 배열 조회에 대한 인수와 같이 이 목적에 사용할 수 있는 여러 가지 유용한 향상된 코드 완성 기능을 제안했습니다. 나는 ChatGPT와 Codex가 그런 일을 하도록 향상될 수 있다고 가정합니다.
이 기사를 즐겼기를 바랍니다. 즐거운 하루 보내시고 오늘(여기 또는 다른 곳에서) 배운 내용을 어제 부러워하게 만드세요!
여기에도 게시됨