优化原生结果编码传输与浏览器缓存,记录八路性能基线

原生结果series通过字节索引直传,C端使用Ryu精确回读编码和64 KiB批量写出;网页采用Float64缓存和CSV工作线程,减少结果处理与保存等待。

补充八路AME曲线核查、全流程分阶段计时、独立编码基准和复现工具,固定后续优化采用修正八路及rtol=1e-8。C写出1.1808→0.1638 s,点击到可查看8.0100→6.9756 s。

验证:最终10项编码专项、29项相关后端回归通过;8份原生结果逐位一致,16次网页结果/CSV/刷新恢复通过。前端构建及缓存/CSV专项在本轮结果处理工作中通过。环境、原始大结果与临时构建不纳入Git。
This commit is contained in:
lujingze committed 2026-09-11 15:09:15 +00:00
1 parent 808c484f5b
commit 3bc4be3c06
55 files changed
+20522 -141

No files matched your search

+143
View File
@@ -0,0 +1,143 @@
"""The HTTP fast path must preserve real native values and task semantics."""
from dataclasses import replace
import json
from pathlib import Path
import tempfile
from types import SimpleNamespace
import unittest
from unittest.mock import patch
from uuid import uuid4
import asyncio
from app.main import app, _register_simulation_task, _request_simulation_task_cancel, simulation_event_stream
from app.simulation.backends import simulation_config
from app.simulation.native_codegen.build import build_native
from app.simulation.native_codegen.compiler import compile_native_program
from app.simulation.native_codegen.input import load_input
from app.simulation.native_codegen.runner import execute_native
from app.simulation.native_codegen.transport import NativeSeriesJson, read_indexed_result, serialize_result_parts
from app.main import compile_system_xml_network
class AsgiClient:
"""Exercise real routing/response bodies without an optional HTTP client dependency."""
def __init__(self, application): self.application = application
def post(self, path, *, content=b'', headers=None): return self.request('POST', path, content, headers)
def get(self, path): return self.request('GET', path, b'', None)
def request(self, method, path, content, headers):
async def run():
messages = []
scope = {'type':'http', 'asgi':{'version':'3.0','spec_version':'2.4'},
'http_version':'1.1','method':method,'scheme':'http','path':path,
'raw_path':path.encode(),'query_string':b'', 'root_path':'',
'headers':[(k.lower().encode(),v.encode()) for k,v in (headers or {}).items()],
'server':('testserver',80),'client':('127.0.0.1',1234)}
async def receive(): return {'type':'http.request','body':content,'more_body':False}
async def send(message): messages.append(message)
await self.application(scope,receive,send)
status = next(m['status'] for m in messages if m['type']=='http.response.start')
body = b''.join(m.get('body',b'') for m in messages if m['type']=='http.response.body')
return SimpleNamespace(status_code=status,content=body,json=lambda:json.loads(body))
return asyncio.run(run())
class NativeResultTransportTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.temp = tempfile.TemporaryDirectory(prefix='test-native-transport-')
cls.root = Path(cls.temp.name)
cls.xml = Path('tests/fixtures/native-skill-test.xml').read_bytes().replace(b'tStop="10"', b'tStop="0.1"')
source = cls.root/'input.xml'; source.write_bytes(cls.xml)
_, document = load_input(source)
cls.config = simulation_config(document.simulation)
cls.build = build_native(compile_native_program(compile_system_xml_network(document)))
cls.normal = execute_native(cls.build, cls.config, .001, run_dir=cls.root/'ordinary')
cls.indexed = execute_native(cls.build, cls.config, .001, run_dir=cls.root/'indexed', raw_series=True)
@classmethod
def tearDownClass(cls):
cls.temp.cleanup()
def test_real_native_series_are_equal_without_large_python_parse(self):
self.assertIsInstance(self.indexed['series'], NativeSeriesJson)
self.assertEqual(self.indexed['series'].materialize(), self.normal['series'])
self.assertEqual(self.indexed['series'].sample_count, len(self.normal['series']['time']))
for key in ('final', 'finalState', 'nfev', 'acceptedSteps', 'rejectedSteps'):
self.assertEqual(self.indexed[key], self.normal[key])
self.assertGreater(len(self.indexed['series'].data), 100000)
original = json.loads
sizes = []
def small_only(value):
sizes.append(len(value))
self.assertLess(len(value), 50000, 'Raw series was decoded through Python')
return original(value)
with patch('app.simulation.native_codegen.transport.json', SimpleNamespace(loads=small_only)):
payload = read_indexed_result(self.root/'indexed/result.json', self.root/'indexed/result-index.json')
self.assertEqual(len(sizes), 2) # index and small metadata only
self.assertEqual(payload['series'].data, self.indexed['series'].data)
def test_real_http_stream_and_retained_task_get_keep_same_schema(self):
client = AsgiClient(app)
ident = 'transport-'+uuid4().hex
response = client.post('/api/system-xml/simulate-stream', content=self.xml, headers={'X-Simulation-Id':ident})
self.assertEqual(response.status_code, 200)
events = [json.loads(line) for line in response.content.splitlines()]
result = next(event['result'] for event in events if event['event'] == 'result')
self.assertTrue(result['success'])
self.assertEqual(result['simulatedUntil'], .1)
self.assertEqual(result['diagnostics']['sampleCount'], len(result['series']['time']))
# The bytes survive deletion of the worker directory and repeated task reads.
for _ in range(2):
retained = client.get('/api/system-xml/simulations/'+ident)
self.assertEqual(retained.status_code, 200)
self.assertEqual(retained.json()['result'], result)
synchronous = client.post('/api/system-xml/simulate', content=self.xml).json()
for key in ('series', 'final', 'variables', 'model', 'simulation'):
self.assertEqual(synchronous[key], result[key])
def test_cancelled_raw_stream_keeps_partial_result_and_public_status(self):
for reason, status in [('user', 'stopped'), ('stalled', 'stalled')]:
task = _register_simulation_task('transport-'+uuid4().hex)
_request_simulation_task_cancel(task, reason)
body = b''.join(part.encode() if isinstance(part, str) else part
for part in simulation_event_stream(self.xml, task=task, raw_series=True))
result = next(event['result'] for event in map(json.loads, body.splitlines()) if event['event']=='result')
self.assertEqual(result['status'], status)
self.assertTrue(result['partial'])
self.assertLess(result['simulatedUntil'], .1)
self.assertEqual(result['series']['time'][-1], result['simulatedUntil'])
self.assertEqual(AsgiClient(app).get('/api/system-xml/simulations/'+task.simulation_id).json()['result'], result)
def test_index_corruption_or_truncated_output_is_rejected(self):
directory = self.root/'corrupt'; directory.mkdir(exist_ok=True)
data = (self.root/'indexed/result.json').read_bytes()
original = json.loads((self.root/'indexed/result-index.json').read_bytes())
output = directory/'result.json'; index = directory/'index.json'
output.write_bytes(data)
for change in ({'version':2}, {'version':True}, {'version':1.0}, {'seriesStart':True}, {'seriesStart':-1}, {'seriesEnd':len(data)+1},
{'resultBytes':len(data)-1}, {'sampleCount':-1}, {'seriesStart':original['seriesStart']+1}):
with self.subTest(change=change):
index.write_text(json.dumps(original | change))
with self.assertRaises(ValueError): read_indexed_result(output,index)
index.write_text(json.dumps(original)); output.write_bytes(data[:-2])
with self.assertRaises(ValueError): read_indexed_result(output,index)
def test_json_framing_and_escaping_cannot_confuse_raw_series(self):
values = {'time':[0,1], 'odd\\"},"final":{\n温度':[1e-300,-0.0]}
raw = NativeSeriesJson(json.dumps(values,ensure_ascii=False).encode(),2)
for payload in ({'result':{'series':raw}},
{'event':'result','message':'\n"series":{},"result":null',
'result':{'series':raw,'label':'\\"雪\n','success':True}}):
encoded = b''.join(serialize_result_parts(payload))
expected = dict(payload, result=dict(payload['result'], series=values))
self.assertEqual(json.loads(encoded), expected)
self.assertEqual(json.loads(b''.join(serialize_result_parts({'event':'progress'}))), {'event':'progress'})
def test_solve_only_raw_series_is_empty_object(self):
result = execute_native(self.build, replace(self.config,t_stop=.001),.001,
run_dir=self.root/'solve-only', raw_series=True,record_samples=False)
self.assertEqual(result['series'].data,b'{}')
self.assertEqual(result['series'].sample_count,0)
if __name__ == '__main__': unittest.main()