441 lines
17 KiB
TypeScript
441 lines
17 KiB
TypeScript
|
|
/**
|
|||
|
|
* Protobuf序列化性能测试
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { Component } from '../../../src/ECS/Component';
|
|||
|
|
import { Entity } from '../../../src/ECS/Entity';
|
|||
|
|
import { Scene } from '../../../src/ECS/Scene';
|
|||
|
|
import { SnapshotManager } from '../../../src/Utils/Snapshot/SnapshotManager';
|
|||
|
|
import { ProtobufSerializer } from '../../../src/Utils/Serialization/ProtobufSerializer';
|
|||
|
|
import {
|
|||
|
|
ProtoSerializable,
|
|||
|
|
ProtoFloat,
|
|||
|
|
ProtoInt32,
|
|||
|
|
ProtoString,
|
|||
|
|
ProtoBool
|
|||
|
|
} from '../../../src/Utils/Serialization/ProtobufDecorators';
|
|||
|
|
|
|||
|
|
// 性能测试组件
|
|||
|
|
@ProtoSerializable('PerfPosition')
|
|||
|
|
class PerfPositionComponent extends Component {
|
|||
|
|
@ProtoFloat(1) public x: number = 0;
|
|||
|
|
@ProtoFloat(2) public y: number = 0;
|
|||
|
|
@ProtoFloat(3) public z: number = 0;
|
|||
|
|
|
|||
|
|
constructor(x: number = 0, y: number = 0, z: number = 0) {
|
|||
|
|
super();
|
|||
|
|
this.x = x;
|
|||
|
|
this.y = y;
|
|||
|
|
this.z = z;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@ProtoSerializable('PerfVelocity')
|
|||
|
|
class PerfVelocityComponent extends Component {
|
|||
|
|
@ProtoFloat(1) public vx: number = 0;
|
|||
|
|
@ProtoFloat(2) public vy: number = 0;
|
|||
|
|
@ProtoFloat(3) public vz: number = 0;
|
|||
|
|
|
|||
|
|
constructor(vx: number = 0, vy: number = 0, vz: number = 0) {
|
|||
|
|
super();
|
|||
|
|
this.vx = vx;
|
|||
|
|
this.vy = vy;
|
|||
|
|
this.vz = vz;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@ProtoSerializable('PerfHealth')
|
|||
|
|
class PerfHealthComponent extends Component {
|
|||
|
|
@ProtoInt32(1) public maxHealth: number = 100;
|
|||
|
|
@ProtoInt32(2) public currentHealth: number = 100;
|
|||
|
|
@ProtoBool(3) public isDead: boolean = false;
|
|||
|
|
@ProtoFloat(4) public regenerationRate: number = 0.5;
|
|||
|
|
|
|||
|
|
constructor(maxHealth: number = 100) {
|
|||
|
|
super();
|
|||
|
|
this.maxHealth = maxHealth;
|
|||
|
|
this.currentHealth = maxHealth;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@ProtoSerializable('PerfPlayer')
|
|||
|
|
class PerfPlayerComponent extends Component {
|
|||
|
|
@ProtoString(1) public name: string = '';
|
|||
|
|
@ProtoInt32(2) public level: number = 1;
|
|||
|
|
@ProtoInt32(3) public experience: number = 0;
|
|||
|
|
@ProtoInt32(4) public score: number = 0;
|
|||
|
|
@ProtoBool(5) public isOnline: boolean = true;
|
|||
|
|
|
|||
|
|
constructor(name: string = 'Player', level: number = 1) {
|
|||
|
|
super();
|
|||
|
|
this.name = name;
|
|||
|
|
this.level = level;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 传统JSON序列化组件(用于对比)
|
|||
|
|
class JsonPositionComponent extends Component {
|
|||
|
|
public x: number = 0;
|
|||
|
|
public y: number = 0;
|
|||
|
|
public z: number = 0;
|
|||
|
|
|
|||
|
|
constructor(x: number = 0, y: number = 0, z: number = 0) {
|
|||
|
|
super();
|
|||
|
|
this.x = x;
|
|||
|
|
this.y = y;
|
|||
|
|
this.z = z;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class JsonPlayerComponent extends Component {
|
|||
|
|
public name: string = '';
|
|||
|
|
public level: number = 1;
|
|||
|
|
public experience: number = 0;
|
|||
|
|
public score: number = 0;
|
|||
|
|
public isOnline: boolean = true;
|
|||
|
|
|
|||
|
|
constructor(name: string = 'Player', level: number = 1) {
|
|||
|
|
super();
|
|||
|
|
this.name = name;
|
|||
|
|
this.level = level;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Mock protobuf.js for performance testing
|
|||
|
|
const createMockProtobuf = () => {
|
|||
|
|
const mockEncodedData = new Uint8Array(32); // 模拟32字节的编码数据
|
|||
|
|
mockEncodedData.fill(1);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
parse: jest.fn().mockReturnValue({
|
|||
|
|
root: {
|
|||
|
|
lookupType: jest.fn().mockImplementation((typeName: string) => ({
|
|||
|
|
verify: jest.fn().mockReturnValue(null),
|
|||
|
|
create: jest.fn().mockImplementation((data) => data),
|
|||
|
|
encode: jest.fn().mockReturnValue({
|
|||
|
|
finish: jest.fn().mockReturnValue(mockEncodedData)
|
|||
|
|
}),
|
|||
|
|
decode: jest.fn().mockReturnValue({
|
|||
|
|
x: 10, y: 20, z: 30,
|
|||
|
|
vx: 1, vy: 2, vz: 3,
|
|||
|
|
maxHealth: 100, currentHealth: 80, isDead: false, regenerationRate: 0.5,
|
|||
|
|
name: 'TestPlayer', level: 5, experience: 1000, score: 5000, isOnline: true
|
|||
|
|
}),
|
|||
|
|
toObject: jest.fn().mockImplementation((message) => message)
|
|||
|
|
}))
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
describe('Protobuf序列化性能测试', () => {
|
|||
|
|
let protobufSerializer: ProtobufSerializer;
|
|||
|
|
let snapshotManager: SnapshotManager;
|
|||
|
|
let scene: Scene;
|
|||
|
|
|
|||
|
|
beforeEach(() => {
|
|||
|
|
protobufSerializer = ProtobufSerializer.getInstance();
|
|||
|
|
protobufSerializer.initialize(createMockProtobuf());
|
|||
|
|
|
|||
|
|
snapshotManager = new SnapshotManager();
|
|||
|
|
snapshotManager.initializeProtobuf(createMockProtobuf());
|
|||
|
|
|
|||
|
|
scene = new Scene();
|
|||
|
|
jest.clearAllMocks();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
describe('单组件序列化性能', () => {
|
|||
|
|
const iterations = 1000;
|
|||
|
|
|
|||
|
|
it('应该比较protobuf和JSON序列化速度', () => {
|
|||
|
|
const protobufComponents: PerfPositionComponent[] = [];
|
|||
|
|
const jsonComponents: JsonPositionComponent[] = [];
|
|||
|
|
|
|||
|
|
// 准备测试数据
|
|||
|
|
for (let i = 0; i < iterations; i++) {
|
|||
|
|
protobufComponents.push(new PerfPositionComponent(
|
|||
|
|
Math.random() * 1000,
|
|||
|
|
Math.random() * 1000,
|
|||
|
|
Math.random() * 100
|
|||
|
|
));
|
|||
|
|
|
|||
|
|
jsonComponents.push(new JsonPositionComponent(
|
|||
|
|
Math.random() * 1000,
|
|||
|
|
Math.random() * 1000,
|
|||
|
|
Math.random() * 100
|
|||
|
|
));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 测试Protobuf序列化
|
|||
|
|
const protobufStartTime = performance.now();
|
|||
|
|
let protobufTotalSize = 0;
|
|||
|
|
|
|||
|
|
for (const component of protobufComponents) {
|
|||
|
|
const result = protobufSerializer.serialize(component);
|
|||
|
|
protobufTotalSize += result.size;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const protobufEndTime = performance.now();
|
|||
|
|
const protobufTime = protobufEndTime - protobufStartTime;
|
|||
|
|
|
|||
|
|
// 测试JSON序列化
|
|||
|
|
const jsonStartTime = performance.now();
|
|||
|
|
let jsonTotalSize = 0;
|
|||
|
|
|
|||
|
|
for (const component of jsonComponents) {
|
|||
|
|
const jsonString = JSON.stringify({
|
|||
|
|
x: component.x,
|
|||
|
|
y: component.y,
|
|||
|
|
z: component.z
|
|||
|
|
});
|
|||
|
|
jsonTotalSize += new Blob([jsonString]).size;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const jsonEndTime = performance.now();
|
|||
|
|
const jsonTime = jsonEndTime - jsonStartTime;
|
|||
|
|
|
|||
|
|
// 性能断言
|
|||
|
|
console.log(`\\n=== 单组件序列化性能对比 (${iterations} 次迭代) ===`);
|
|||
|
|
console.log(`Protobuf时间: ${protobufTime.toFixed(2)}ms`);
|
|||
|
|
console.log(`JSON时间: ${jsonTime.toFixed(2)}ms`);
|
|||
|
|
console.log(`Protobuf总大小: ${protobufTotalSize} bytes`);
|
|||
|
|
console.log(`JSON总大小: ${jsonTotalSize} bytes`);
|
|||
|
|
|
|||
|
|
if (jsonTime > 0) {
|
|||
|
|
const speedImprovement = ((jsonTime - protobufTime) / jsonTime * 100);
|
|||
|
|
console.log(`速度提升: ${speedImprovement.toFixed(1)}%`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (jsonTotalSize > 0) {
|
|||
|
|
const sizeReduction = ((jsonTotalSize - protobufTotalSize) / jsonTotalSize * 100);
|
|||
|
|
console.log(`大小减少: ${sizeReduction.toFixed(1)}%`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 基本性能验证
|
|||
|
|
expect(protobufTime).toBeLessThan(1000); // 不应该超过1秒
|
|||
|
|
expect(jsonTime).toBeLessThan(1000);
|
|||
|
|
expect(protobufTotalSize).toBeGreaterThan(0);
|
|||
|
|
expect(jsonTotalSize).toBeGreaterThan(0);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('应该测试复杂组件的序列化性能', () => {
|
|||
|
|
const protobufPlayers: PerfPlayerComponent[] = [];
|
|||
|
|
const jsonPlayers: JsonPlayerComponent[] = [];
|
|||
|
|
|
|||
|
|
// 创建测试数据
|
|||
|
|
for (let i = 0; i < iterations; i++) {
|
|||
|
|
protobufPlayers.push(new PerfPlayerComponent(
|
|||
|
|
`Player${i}`,
|
|||
|
|
Math.floor(Math.random() * 100) + 1
|
|||
|
|
));
|
|||
|
|
|
|||
|
|
jsonPlayers.push(new JsonPlayerComponent(
|
|||
|
|
`Player${i}`,
|
|||
|
|
Math.floor(Math.random() * 100) + 1
|
|||
|
|
));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Protobuf序列化测试
|
|||
|
|
const protobufStart = performance.now();
|
|||
|
|
for (const player of protobufPlayers) {
|
|||
|
|
protobufSerializer.serialize(player);
|
|||
|
|
}
|
|||
|
|
const protobufTime = performance.now() - protobufStart;
|
|||
|
|
|
|||
|
|
// JSON序列化测试
|
|||
|
|
const jsonStart = performance.now();
|
|||
|
|
for (const player of jsonPlayers) {
|
|||
|
|
JSON.stringify({
|
|||
|
|
name: player.name,
|
|||
|
|
level: player.level,
|
|||
|
|
experience: player.experience,
|
|||
|
|
score: player.score,
|
|||
|
|
isOnline: player.isOnline
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
const jsonTime = performance.now() - jsonStart;
|
|||
|
|
|
|||
|
|
console.log(`\\n=== 复杂组件序列化性能 (${iterations} 次迭代) ===`);
|
|||
|
|
console.log(`Protobuf时间: ${protobufTime.toFixed(2)}ms`);
|
|||
|
|
console.log(`JSON时间: ${jsonTime.toFixed(2)}ms`);
|
|||
|
|
|
|||
|
|
expect(protobufTime).toBeLessThan(1000);
|
|||
|
|
expect(jsonTime).toBeLessThan(1000);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
describe('批量实体序列化性能', () => {
|
|||
|
|
it('应该测试大量实体的快照创建性能', () => {
|
|||
|
|
const entityCount = 100;
|
|||
|
|
const entities: Entity[] = [];
|
|||
|
|
|
|||
|
|
// 创建测试实体
|
|||
|
|
for (let i = 0; i < entityCount; i++) {
|
|||
|
|
const entity = scene.createEntity(`Entity${i}`);
|
|||
|
|
entity.addComponent(new PerfPositionComponent(
|
|||
|
|
Math.random() * 1000,
|
|||
|
|
Math.random() * 1000,
|
|||
|
|
Math.random() * 100
|
|||
|
|
));
|
|||
|
|
entity.addComponent(new PerfVelocityComponent(
|
|||
|
|
Math.random() * 10 - 5,
|
|||
|
|
Math.random() * 10 - 5,
|
|||
|
|
Math.random() * 2 - 1
|
|||
|
|
));
|
|||
|
|
entity.addComponent(new PerfHealthComponent(100 + Math.floor(Math.random() * 50)));
|
|||
|
|
entity.addComponent(new PerfPlayerComponent(`Player${i}`, Math.floor(Math.random() * 50) + 1));
|
|||
|
|
|
|||
|
|
entities.push(entity);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 测试快照创建性能
|
|||
|
|
const snapshotStart = performance.now();
|
|||
|
|
const snapshot = snapshotManager.createSceneSnapshot(entities);
|
|||
|
|
const snapshotTime = performance.now() - snapshotStart;
|
|||
|
|
|
|||
|
|
console.log(`\\n=== 批量实体序列化性能 ===`);
|
|||
|
|
console.log(`实体数量: ${entityCount}`);
|
|||
|
|
console.log(`每个实体组件数: 4`);
|
|||
|
|
console.log(`总组件数: ${entityCount * 4}`);
|
|||
|
|
console.log(`快照创建时间: ${snapshotTime.toFixed(2)}ms`);
|
|||
|
|
console.log(`平均每组件时间: ${(snapshotTime / (entityCount * 4)).toFixed(3)}ms`);
|
|||
|
|
|
|||
|
|
expect(snapshot.entities).toHaveLength(entityCount);
|
|||
|
|
expect(snapshotTime).toBeLessThan(5000); // 不应该超过5秒
|
|||
|
|
|
|||
|
|
// 计算快照大小
|
|||
|
|
let totalSnapshotSize = 0;
|
|||
|
|
for (const entitySnapshot of snapshot.entities) {
|
|||
|
|
for (const componentSnapshot of entitySnapshot.components) {
|
|||
|
|
if (componentSnapshot.data && typeof componentSnapshot.data === 'object' && 'size' in componentSnapshot.data) {
|
|||
|
|
totalSnapshotSize += (componentSnapshot.data as any).size;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`快照总大小: ${totalSnapshotSize} bytes`);
|
|||
|
|
console.log(`平均每实体大小: ${(totalSnapshotSize / entityCount).toFixed(1)} bytes`);
|
|||
|
|
|
|||
|
|
expect(totalSnapshotSize).toBeGreaterThan(0);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
describe('反序列化性能', () => {
|
|||
|
|
it('应该测试快照恢复性能', () => {
|
|||
|
|
const entityCount = 50;
|
|||
|
|
const originalEntities: Entity[] = [];
|
|||
|
|
|
|||
|
|
// 创建原始实体
|
|||
|
|
for (let i = 0; i < entityCount; i++) {
|
|||
|
|
const entity = scene.createEntity(`Original${i}`);
|
|||
|
|
entity.addComponent(new PerfPositionComponent(i * 10, i * 20, i));
|
|||
|
|
entity.addComponent(new PerfHealthComponent(100 + i));
|
|||
|
|
originalEntities.push(entity);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建快照
|
|||
|
|
const snapshotStart = performance.now();
|
|||
|
|
const snapshot = snapshotManager.createSceneSnapshot(originalEntities);
|
|||
|
|
const snapshotTime = performance.now() - snapshotStart;
|
|||
|
|
|
|||
|
|
// 创建目标实体
|
|||
|
|
const targetEntities: Entity[] = [];
|
|||
|
|
for (let i = 0; i < entityCount; i++) {
|
|||
|
|
const entity = scene.createEntity(`Target${i}`);
|
|||
|
|
entity.addComponent(new PerfPositionComponent());
|
|||
|
|
entity.addComponent(new PerfHealthComponent());
|
|||
|
|
targetEntities.push(entity);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 测试恢复性能
|
|||
|
|
const restoreStart = performance.now();
|
|||
|
|
snapshotManager.restoreFromSnapshot(snapshot, targetEntities);
|
|||
|
|
const restoreTime = performance.now() - restoreStart;
|
|||
|
|
|
|||
|
|
console.log(`\\n=== 反序列化性能测试 ===`);
|
|||
|
|
console.log(`实体数量: ${entityCount}`);
|
|||
|
|
console.log(`序列化时间: ${snapshotTime.toFixed(2)}ms`);
|
|||
|
|
console.log(`反序列化时间: ${restoreTime.toFixed(2)}ms`);
|
|||
|
|
console.log(`总往返时间: ${(snapshotTime + restoreTime).toFixed(2)}ms`);
|
|||
|
|
console.log(`平均每实体往返时间: ${((snapshotTime + restoreTime) / entityCount).toFixed(3)}ms`);
|
|||
|
|
|
|||
|
|
expect(restoreTime).toBeLessThan(2000); // 不应该超过2秒
|
|||
|
|
expect(snapshotTime + restoreTime).toBeLessThan(3000); // 总时间不超过3秒
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
describe('内存使用', () => {
|
|||
|
|
it('应该监控序列化过程中的内存使用', () => {
|
|||
|
|
const entityCount = 200;
|
|||
|
|
const entities: Entity[] = [];
|
|||
|
|
|
|||
|
|
// 创建大量实体
|
|||
|
|
for (let i = 0; i < entityCount; i++) {
|
|||
|
|
const entity = scene.createEntity(`MemoryTest${i}`);
|
|||
|
|
entity.addComponent(new PerfPositionComponent(
|
|||
|
|
Math.random() * 1000,
|
|||
|
|
Math.random() * 1000,
|
|||
|
|
Math.random() * 100
|
|||
|
|
));
|
|||
|
|
entity.addComponent(new PerfVelocityComponent(
|
|||
|
|
Math.random() * 10,
|
|||
|
|
Math.random() * 10,
|
|||
|
|
Math.random() * 2
|
|||
|
|
));
|
|||
|
|
entity.addComponent(new PerfHealthComponent(Math.floor(Math.random() * 200) + 50));
|
|||
|
|
entities.push(entity);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 记录初始内存(如果可用)
|
|||
|
|
const initialMemory = (performance as any).memory?.usedJSHeapSize || 0;
|
|||
|
|
|
|||
|
|
// 执行序列化
|
|||
|
|
const snapshot = snapshotManager.createSceneSnapshot(entities);
|
|||
|
|
|
|||
|
|
// 记录序列化后内存
|
|||
|
|
const afterMemory = (performance as any).memory?.usedJSHeapSize || 0;
|
|||
|
|
const memoryIncrease = afterMemory - initialMemory;
|
|||
|
|
|
|||
|
|
if (initialMemory > 0) {
|
|||
|
|
console.log(`\\n=== 内存使用测试 ===`);
|
|||
|
|
console.log(`实体数量: ${entityCount}`);
|
|||
|
|
console.log(`初始内存: ${(initialMemory / 1024 / 1024).toFixed(2)} MB`);
|
|||
|
|
console.log(`序列化后内存: ${(afterMemory / 1024 / 1024).toFixed(2)} MB`);
|
|||
|
|
console.log(`内存增加: ${(memoryIncrease / 1024).toFixed(2)} KB`);
|
|||
|
|
console.log(`平均每实体内存: ${(memoryIncrease / entityCount).toFixed(1)} bytes`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
expect(snapshot.entities).toHaveLength(entityCount);
|
|||
|
|
|
|||
|
|
// 清理
|
|||
|
|
entities.length = 0;
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
describe('极端情况性能', () => {
|
|||
|
|
it('应该处理大量小组件的性能', () => {
|
|||
|
|
const componentCount = 5000;
|
|||
|
|
const components: PerfPositionComponent[] = [];
|
|||
|
|
|
|||
|
|
// 创建大量小组件
|
|||
|
|
for (let i = 0; i < componentCount; i++) {
|
|||
|
|
components.push(new PerfPositionComponent(i, i * 2, i * 3));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const start = performance.now();
|
|||
|
|
for (const component of components) {
|
|||
|
|
protobufSerializer.serialize(component);
|
|||
|
|
}
|
|||
|
|
const time = performance.now() - start;
|
|||
|
|
|
|||
|
|
console.log(`\\n=== 大量小组件性能测试 ===`);
|
|||
|
|
console.log(`组件数量: ${componentCount}`);
|
|||
|
|
console.log(`总时间: ${time.toFixed(2)}ms`);
|
|||
|
|
console.log(`平均每组件: ${(time / componentCount).toFixed(4)}ms`);
|
|||
|
|
console.log(`每秒处理: ${Math.floor(componentCount / (time / 1000))} 个组件`);
|
|||
|
|
|
|||
|
|
expect(time).toBeLessThan(10000); // 不超过10秒
|
|||
|
|
expect(time / componentCount).toBeLessThan(2); // 每个组件不超过2ms
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|