food_chain/experiments/cost_audit.mjs
2026-09-29 23:45:12 +09:00

40 lines
9.6 KiB
JavaScript

import fs from 'node:fs/promises';
import vm from 'node:vm';
import {performance} from 'node:perf_hooks';
const html=await fs.readFile(new URL('../index.html',import.meta.url),'utf8');
const scripts=[...html.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)].map(m=>m[1]);
const kc={};vm.runInNewContext(scripts[0],kc);const kernels=kc.MANDEL_WEBGPU_KERNELS;
const element={width:1024,height:768,clientWidth:1024,clientHeight:768,style:{},hidden:false,value:'',textContent:'',disabled:false,addEventListener(){},classList:{toggle(){}},setAttribute(){},removeAttribute(){},showModal(){},close(){},toBlob(){},getBoundingClientRect(){return{left:0,top:0,width:this.clientWidth,height:this.clientHeight}}};
const app={MANDEL_WEBGPU_KERNELS:kernels,document:{querySelector:()=>element,getElementById:()=>element,documentElement:{classList:{toggle(){}}},body:{classList:{toggle(){}},appendChild(){}}},navigator:{hardwareConcurrency:8,deviceMemory:8},location:{search:'',hash:''},history:{replaceState(){}},performance,URLSearchParams,URL:{createObjectURL:()=>'',revokeObjectURL(){}},TextEncoder,Blob:class{},console,addEventListener(){},requestAnimationFrame:()=>1,cancelAnimationFrame(){},setTimeout,clearTimeout,setInterval,clearInterval,innerWidth:1024,innerHeight:768,devicePixelRatio:1,localStorage:{getItem(){return null},setItem(){},removeItem(){}}};
const cut=scripts[1].indexOf('// ── boot / teardown');
vm.runInNewContext(scripts[1].slice(0,cut)+`\nglobalThis.auditInternal={state,canvas,snapshot,maxIter,zoomExp,pixelPrecisionBits,adaptiveProbeSources,adaptiveProbeChoice,referenceWorkerSource,certifiedInteriorTiles,integerGridReuseShift,fmtFixed,updateStats};})();`,app);
const I=app.auditInternal;
const corpus=JSON.parse(await fs.readFile(new URL('./benchmark-corpus.json',import.meta.url),'utf8')).views;
const median=a=>{const b=[...a].sort((x,y)=>x-y),m=b.length>>1;return b.length%2?b[m]:(b[m-1]+b[m])/2};
function fromDec(s,bits=256){s=String(s).trim();let neg=s.startsWith('-');if(neg)s=s.slice(1);if(s.startsWith('+'))s=s.slice(1);const [mant,es='0']=s.toLowerCase().split('e'),exp=parseInt(es,10)||0,[i='0',fr='']=mant.split('.');let digits=(i+fr).replace(/^0+(?=\d)/,'')||'0',places=fr.length-exp;if(places<0){digits+='0'.repeat(-places);places=0}const den=10n**BigInt(places),v=(BigInt(digits)*(1n<<BigInt(bits))+den/2n)/den;return neg?-v:v}
function snapOf(v,bits=256){return{bits,re:fromDec(v.re,bits),im:fromDec(v.im,bits),span:fromDec(v.span,bits)}}
function setView(v,w=1024,h=768){I.canvas.width=w;I.canvas.height=h;I.canvas.clientWidth=w;I.canvas.clientHeight=h;const s=snapOf(v);Object.assign(I.state,s);return s}
function benchInteriorProof(){const sizes=[[1024,768],[2048,2048]],rows=[];for(const [w,h] of sizes){for(const v of corpus){const snap=setView(v,w,h);const times=[];let r;for(let k=0;k<7;k++){const t0=performance.now();r=I.certifiedInteriorTiles(snap,w,h,32,null);times.push(performance.now()-t0)}rows.push({view:v.name,w,h,pixels:w*h,certified:r.pixels,certifiedRatio:r.pixels/(w*h),medianMs:median(times),times})}}return rows}
function runScoreWorker(snap,iter,w,h,candidates){let result;const c={performance,postMessage:m=>{result=m},BigInt,Float32Array,Float64Array,ArrayBuffer,DataView,Math,Number,String,Map,Set,Uint32Array};c.self=c;vm.runInNewContext(I.referenceWorkerSource(),c);const targetBits=Math.max(I.pixelPrecisionBits(snap,w,40)+32,...candidates.map(x=>x.precisionBits||0));c.self.onmessage({data:{type:'score',id:1,bits:snap.bits,targetBits,re:snap.re.toString(),im:snap.im.toString(),span:snap.span.toString(),width:w,height:h,iter,candidates:candidates.map(x=>({re:x.re.toString(),im:x.im.toString()}))}});if(result?.type==='error')throw new Error(result.error);return result}
function benchAdaptiveProbe(){const w=1024,h=768,rows=[];for(const v of corpus){const snap=setView(v,w,h),adaptiveIter=I.maxIter(),candidates=I.adaptiveProbeSources(snap,w,h);const times=[];let res;for(let k=0;k<3;k++){const t0=performance.now();res=runScoreWorker(snap,adaptiveIter,w,h,candidates);times.push(performance.now()-t0)}const choice=I.adaptiveProbeChoice(res.escapes,adaptiveIter,I.state.baseIter);rows.push({view:v.name,adaptiveIter,chosenInitialIter:choice.iter,targetBits:I.pixelPrecisionBits(snap,w,40)+32,samples:candidates.length,scoreReportedMs:res.scoreMs,wallMedianMs:median(times),escapeSamples:res.escapes,workChoices:choice.work})}return rows}
const f=Math.fround;
function analyticF32(cr,ci){cr=f(cr);ci=f(ci);const y2=f(ci*ci),x=f(cr-.25),q=f(f(x*x)+y2),lhs=f(q*f(q+x)),rhs=f(.25*y2),margin=f(16*5.960464477539063e-8*f(f(Math.abs(lhs)+Math.abs(rhs))+1));if(lhs<f(rhs-margin))return true;const x2=f(cr+1),bulb=f(f(x2*x2)+y2),bm=f(16*5.960464477539063e-8*f(f(Math.abs(bulb)+.0625)+1));return bulb<f(.0625-bm)}
function coordF32(view,x,y,w,h){const scale=f(f(view.span)/f(w)),cr=f(f(view.re)+f(f(f(x+.5)-f(.5*w))*scale)),ci=f(f(view.im)+f(f(f(.5*h)-f(y+.5))*scale));return[cr,ci]}
function cmul(a,b){return[f(f(a[0]*b[0])-f(a[1]*b[1])),f(f(a[0]*b[1])+f(a[1]*b[0]))]}
function directBase(cr,ci,maxIter){if(analyticF32(cr,ci))return 0;let zr=f(0),zi=f(0);for(let n=1;n<=maxIter;n++){const zr2=f(zr*zr),zi2=f(zi*zi);zi=f(f(2*zr*zi)+ci);zr=f(f(zr2-zi2)+cr);if(f(f(zr*zr)+f(zi*zi))>4)return n}return maxIter}
function directPeriodic(cr,ci,maxIter){if(analyticF32(cr,ci))return 0;let zr=f(0),zi=f(0),n=0,cycleR=f(0),cycleI=f(0),cyclePower=1,cycleLam=0,candPeriod=0,candAge=0,candR=f(0),candI=f(0),candMul=[f(1),f(0)],candRepeats=0;while(n<maxIter){const prev=[zr,zi],zr2=f(zr*zr),zi2=f(zi*zi);zi=f(f(2*zr*zi)+ci);zr=f(f(zr2-zi2)+cr);n++;if(f(f(zr*zr)+f(zi*zi))>4)return n;if(candPeriod>0){candMul=cmul(candMul,[f(2*prev[0]),f(2*prev[1])]);candAge++;if(candAge>=candPeriod){const tol=f(4*5.960464477539063e-8*f(1+Math.max(Math.abs(zr),Math.abs(zi)))),close=Math.max(Math.abs(f(zr-candR)),Math.abs(f(zi-candI)))<=tol,attracting=f(f(candMul[0]*candMul[0])+f(candMul[1]*candMul[1]))<.9025;if(close&&attracting){candRepeats++;if(candRepeats>=2)return n;candR=zr;candI=zi;candAge=0;candMul=[f(1),f(0)]}else{candPeriod=0;candRepeats=0}}}if(candPeriod===0&&n>=64){const tol=f(4*5.960464477539063e-8*f(1+Math.max(Math.abs(zr),Math.abs(zi))));if(Math.max(Math.abs(f(zr-cycleR)),Math.abs(f(zi-cycleI)))<=tol){const period=cycleLam+1;if(period<=2048){candPeriod=period;candAge=0;candR=zr;candI=zi;candMul=[f(1),f(0)];candRepeats=0}}}cycleLam++;if(cycleLam>=cyclePower){cycleR=zr;cycleI=zi;cyclePower=Math.min(65536,cyclePower*2);cycleLam=0}}return maxIter}
function benchPeriodicityRuntime(){const w=64,h=40,maxIter=4096,rows=[];for(const v of corpus){const pts=[];for(let y=0;y<h;y++)for(let x=0;x<w;x++)pts.push(coordF32(v,x,y,w,h));const run=fn=>{let sum=0;const t0=performance.now();for(const [cr,ci] of pts)sum+=fn(cr,ci,maxIter);return{ms:performance.now()-t0,sum}};run(directBase);run(directPeriodic);const b=[],p=[];for(let k=0;k<4;k++){b.push(run(directBase));p.push(run(directPeriodic))}rows.push({view:v.name,baseMedianMs:median(b.map(x=>x.ms)),periodicMedianMs:median(p.map(x=>x.ms)),runtimeRatio:median(p.map(x=>x.ms))/median(b.map(x=>x.ms)),baseIterationSum:b.at(-1).sum,periodicIterationSum:p.at(-1).sum,iterationReduction:1-p.at(-1).sum/Math.max(1,b.at(-1).sum)})}return rows}
function memoryAudit(){const presets={fast:524288,standard:1048576,high:4194304},rows=[];for(const [preset,n] of Object.entries(presets)){rows.push({preset,pixels:n,baseFrameBytes:28*n,numericHistoryBytes:8*n,colorSourceBytes:12*n,deepCorrectionQueueBytes:4*n,cpuFallbackRgbaBytes:4*n,colorAutoNumericHistoryCopyBytesPerSecond:8*n*20,provisionalSnapshotCopyBytesPerSecondAt8Hz:8*n*8})}const export512PerSlot=(512*512)*(4+4+4*4+4+4); // meta,smooth,4 samples,resolve,read
const export256PerSlot=(256*256)*(4+4+4*4+4+4);
return{rows,export:{tile512PerSlotBytes:export512PerSlot,tile512Depth3Bytes:3*export512PerSlot,tile256PerSlotBytes:export256PerSlot,tile256Depth2Bytes:2*export256PerSlot,nonAaUnusedSampleTextureBytesPer512Slot:4*512*512*4}}}
function gridReusePanAudit(){const rows=[];for(const dpr of [1,1.25,1.5,2])for(const cssW of [800,1024,1365]){const w=Math.round(cssW*dpr),h=Math.round(w*.75),old={...snapOf(corpus[0]),w,h};for(const dx of [1,3,10,37,100]){const denom=BigInt(Math.round(cssW*1e6)),re=old.re-old.span*BigInt(Math.round(dx*1e6))/denom,newv={...old,re};const sh=I.integerGridReuseShift(old,newv,w,h);rows.push({dpr,cssW,w,dx,reused:!!sh,sx:sh?.sx??null,pixels:sh?.pixels??0,ratio:sh?sh.pixels/(w*h):0})}}return rows}
const results={date:new Date().toISOString(),sourceSha256:null,interiorProof:benchInteriorProof(),adaptiveProbe:benchAdaptiveProbe(),periodicityRuntimeSurrogate:benchPeriodicityRuntime(),memory:memoryAudit(),gridReusePan:gridReusePanAudit(),staticFindings:{certifyInteriorRunsUnusedUnknownStats:/encodeUnknownStats\(e,\{meta:f\.meta,unresolved:f\.unresolved,n:f\.n\}\);\s*return await this\.submitStage\(e,historySeed\?/.test(html),numericHistoryAlwaysAllocated:/historyMeta:buf\(d,n\*4/.test(html)&&/historySmooth:buf\(d,n\*4/.test(html),commitHistoryAlwaysCommitsNumeric:/this\.commitNumericHistory\(view,state\.fieldView\?\.iter/.test(html),colorAuto20Hz:/state\.rendering\?100:50/.test(html),colorSourceSnapshotCopiesNumeric:/copyBufferToBuffer\(f\.meta,0,source\.meta,0,f\.n\*4\)/.test(html)&&/copyBufferToBuffer\(f\.smooth,0,source\.smooth,0,f\.n\*4\)/.test(html),cpuFallbackRgbaUnused:(html.match(/cpuFrameRgba/g)||[]).length===2,exportWorkspacePersists:!/exportWorkspaceDestroy\(\)/.test(html.slice(html.indexOf('async function runExport'),html.indexOf("$('png').onclick"))),fastCoverageRepair:/fast-coverage-repair/.test(html)&&/this\.encodeFastNumeric\(e,\{pbuf:f\.numericParams/.test(html)}};
console.log(JSON.stringify(results,null,2));
if(process.argv[2])await fs.writeFile(process.argv[2],JSON.stringify(results,null,2)+'\n');