ECharts-GL 성능 이슈 분석 및 최적화 방안
목차
성능 프로파일링
WebGL 기반 3D 시각화는 강력한 표현력을 제공하지만, 성능 이슈가 발생할 수 있습니다. ECharts-GL의 성능을 분석하고 최적화하기 위한 프로파일링 방법을 살펴보겠습니다.
WebGL 렌더링 성능 분석
주요 성능 지표
WebGL 렌더링 성능을 측정하는 주요 지표는 다음과 같습니다:
- FPS (Frames Per Second): 초당 프레임 수로, 60 FPS 이상이 이상적입니다.
- Draw Calls: GPU에 전송되는 렌더링 명령의 수로, 적을수록 좋습니다.
- Vertices: 렌더링되는 정점의 수로, 복잡한 장면일수록 증가합니다.
- Triangles: 렌더링되는 삼각형의 수로, 3D 모델의 복잡도를 나타냅니다.
- Memory Usage: GPU 및 CPU 메모리 사용량으로, 텍스처와 버퍼 크기에 영향을 받습니다.
- Shader Complexity: 셰이더 프로그램의 복잡도로, 계산 비용에 영향을 줍니다.
성능 측정 도구
ECharts-GL 애플리케이션의 성능을 측정하기 위한 도구는 다음과 같습니다:
브라우저 개발자 도구:
- Chrome DevTools의 Performance 패널
- Firefox의 Performance 패널
- Edge의 Performance 패널
WebGL 인스펙터:
- Spector.js: WebGL 호출 및 상태 분석
- WebGL Inspector: WebGL 컨텍스트 디버깅
ECharts-GL 내장 성능 모니터:
// 성능 모니터 활성화 chart.setOption({ debug: { showFPS: true, showDrawCallCount: true, showTriangleCount: true } });
성능 프로파일링 방법
ECharts-GL 애플리케이션의 성능을 프로파일링하는 단계는 다음과 같습니다:
기준 성능 측정:
// 성능 측정 시작 console.time('Rendering'); // 차트 렌더링 chart.setOption(option); // 성능 측정 종료 console.timeEnd('Rendering');프레임 타임 측정:
let lastTime = performance.now(); let frames = 0; let totalTime = 0; function measure() { const now = performance.now(); const frameTime = now - lastTime; lastTime = now; frames++; totalTime += frameTime; if (frames === 60) { console.log(`Average frame time: ${totalTime / frames}ms`); console.log(`FPS: ${1000 / (totalTime / frames)}`); frames = 0; totalTime = 0; } requestAnimationFrame(measure); } requestAnimationFrame(measure);메모리 사용량 측정:
// Chrome DevTools에서 메모리 스냅샷 생성 // 또는 코드로 측정 const memoryInfo = performance.memory; console.log(`Total JS heap size: ${memoryInfo.totalJSHeapSize / (1024 * 1024)} MB`); console.log(`Used JS heap size: ${memoryInfo.usedJSHeapSize / (1024 * 1024)} MB`);
GPU 최적화 요소
ECharts-GL은 다양한 GPU 최적화 기법을 활용하여 렌더링 성능을 향상시킵니다.
Frustum Culling
Frustum Culling은 카메라의 시야(Frustum) 밖에 있는 객체를 렌더링하지 않는 기법입니다.
graph TD
A[장면 객체] --> B{시야 내에 있는가?}
B -->|Yes| C[렌더링]
B -->|No| D[렌더링 생략]
ECharts-GL에서는 다음과 같이 구현됩니다:
// ViewGL 클래스 내부 구현
ViewGL.prototype.isMeshInViewFrustum = function (mesh) {
// 메시의 바운딩 박스 계산
if (!mesh.boundingBox) {
mesh.updateBoundingBox();
}
// 바운딩 박스와 시야 프러스텀 교차 테스트
return this.camera.frustum.intersectsBox(mesh.boundingBox);
};
// 렌더링 시 적용
Scene.prototype.renderPass = function (renderer, camera) {
this.traverse(function (mesh) {
if (mesh.isRenderable() && viewGL.isMeshInViewFrustum(mesh)) {
renderer.renderPass(mesh, camera);
}
});
};
Instancing
Instancing은 동일한 기하 데이터를 가진 여러 객체를 효율적으로 렌더링하는 기법입니다.
graph TD
A[기하 데이터] --> B[인스턴스 버퍼]
B --> C[인스턴스 렌더링]
C --> D[단일 드로우 콜]
ECharts-GL에서는 다음과 같이 구현됩니다:
// 인스턴싱 활성화 (예: 산점도 차트)
var geometry = new graphicGL.Geometry();
// 기하 데이터 설정...
// 인스턴스 속성 추가
geometry.attributes.instancePosition = new graphicGL.Attribute('instancePosition', 'float', 3);
geometry.attributes.instanceColor = new graphicGL.Attribute('instanceColor', 'float', 4);
// 인스턴스 데이터 설정
var instanceCount = data.count();
geometry.attributes.instancePosition.init(instanceCount);
geometry.attributes.instanceColor.init(instanceCount);
// 셰이더에서 인스턴스 속성 사용
var material = new graphicGL.Material({
shader: graphicGL.createShader('ecgl.point'),
transparent: true,
depthMask: false
});
material.define('vertex', 'USE_INSTANCING');
// 인스턴스 렌더링
var mesh = new graphicGL.Mesh({
geometry: geometry,
material: material,
mode: graphicGL.Mesh.POINTS
});
mesh.geometry.enableInstancing = true;
Level of Detail (LOD)
LOD는 카메라와의 거리에 따라 객체의 상세도를 조절하는 기법입니다.
graph TD
A[객체] --> B{카메라와의 거리?}
B -->|가까움| C[고해상도 모델]
B -->|중간| D[중간 해상도 모델]
B -->|멀리| E[저해상도 모델]
ECharts-GL에서는 다음과 같이 구현됩니다:
// Globe 컴포넌트에서의 LOD 구현 예시
Globe.prototype.update = function () {
// 카메라 거리에 따른 지형 해상도 조절
var distance = this.viewGL.camera.getDistance();
var detailLevel;
if (distance < 500) {
detailLevel = 'high';
this._earthMesh.geometry.setDetailLevel(32);
}
else if (distance < 1500) {
detailLevel = 'medium';
this._earthMesh.geometry.setDetailLevel(16);
}
else {
detailLevel = 'low';
this._earthMesh.geometry.setDetailLevel(8);
}
// 텍스처 해상도도 조절
this._setTextureLOD(detailLevel);
};
최적화 방법
ECharts-GL 애플리케이션의 성능을 최적화하기 위한 방법을 살펴보겠습니다.
드로우콜 최적화
드로우콜(Draw Call)은 GPU에 렌더링 명령을 전송하는 작업으로, 많을수록 CPU-GPU 간 통신 오버헤드가 증가합니다.
드로우콜 감소 방법
- 배칭(Batching): 유사한 객체를 하나의 드로우콜로 렌더링
// 배칭 예시: 여러 개의 작은 메시를 하나로 병합
function batchMeshes(meshes) {
var totalVertexCount = 0;
var totalTriangleCount = 0;
// 총 정점 및 삼각형 수 계산
meshes.forEach(function (mesh) {
totalVertexCount += mesh.geometry.vertexCount;
totalTriangleCount += mesh.geometry.triangleCount;
});
// 새 기하 데이터 생성
var geometry = new graphicGL.Geometry();
geometry.attributes.position.init(totalVertexCount);
geometry.attributes.normal.init(totalVertexCount);
geometry.attributes.texcoord0.init(totalVertexCount);
var indices = new Uint32Array(totalTriangleCount * 3);
// 모든 메시의 데이터를 새 기하 데이터에 복사
var vertexOffset = 0;
var triangleOffset = 0;
meshes.forEach(function (mesh) {
// 정점 데이터 복사
// 인덱스 데이터 복사 및 오프셋 적용
// ...
vertexOffset += mesh.geometry.vertexCount;
triangleOffset += mesh.geometry.triangleCount;
});
geometry.indices = indices;
// 새 메시 생성
var material = meshes[0].material;
var batchedMesh = new graphicGL.Mesh({
geometry: geometry,
material: material
});
return batchedMesh;
}
- 인스턴싱(Instancing): 동일한 기하 데이터를 가진 객체를 효율적으로 렌더링
// 인스턴싱 활성화 예시
function enableInstancing(chart) {
var seriesModel = chart.getModel().getSeriesByType('scatter3D')[0];
var data = seriesModel.getData();
// 인스턴스 속성 설정
// ...
// 인스턴싱 활성화
chart.getZr().__egl.update(chart.getModel(), chart.getZr().painter);
}
- 지오메트리 병합: 유사한 지오메트리를 하나로 병합
// 지오메트리 병합 예시
function mergeGeometries(geometries) {
// 새 지오메트리 생성
var mergedGeometry = new graphicGL.Geometry();
// 각 지오메트리의 데이터를 병합
// ...
return mergedGeometry;
}
메모리 최적화
WebGL 애플리케이션에서 메모리 관리는 성능에 큰 영향을 미칩니다.
메모리 사용량 감소 방법
- 텍스처 최적화: 적절한 크기와 포맷의 텍스처 사용
// 텍스처 최적화 예시
function optimizeTexture(texture) {
// 텍스처 크기 조정
texture.width = nearestPowerOfTwo(texture.width);
texture.height = nearestPowerOfTwo(texture.height);
// 밉맵 생성
texture.generateMipmap();
// 압축 텍스처 포맷 사용 (지원되는 경우)
if (renderer.getGLExtension('WEBGL_compressed_texture_s3tc')) {
texture.format = 'COMPRESSED_RGB_S3TC_DXT1_EXT';
}
return texture;
}
function nearestPowerOfTwo(value) {
return Math.pow(2, Math.round(Math.log(value) / Math.log(2)));
}
- 버퍼 재사용: 버퍼 객체를 재사용하여 가비지 컬렉션 감소
// 버퍼 재사용 예시
var BufferPool = {
_pool: {},
getBuffer: function (size, type) {
var key = size + '_' + type;
if (!this._pool[key]) {
this._pool[key] = new (type === 'float' ? Float32Array : Uint16Array)(size);
}
return this._pool[key];
},
releaseBuffer: function (buffer) {
// 버퍼를 풀에 반환
var key = buffer.length + '_' + (buffer instanceof Float32Array ? 'float' : 'uint16');
this._pool[key] = buffer;
}
};
- 지연 로딩: 필요한 리소스만 로드하여 초기 메모리 사용량 감소
// 지연 로딩 예시
function lazyLoadTexture(url, callback) {
// 텍스처가 화면에 보일 때만 로드
var observer = new IntersectionObserver(function (entries) {
if (entries[0].isIntersecting) {
var texture = new graphicGL.Texture2D();
texture.load(url).then(function () {
callback(texture);
});
observer.disconnect();
}
});
observer.observe(document.getElementById('main'));
}
지오메트리 최적화
3D 모델의 지오메트리 최적화는 렌더링 성능 향상에 중요합니다.
지오메트리 최적화 방법
- 정점 감소: 불필요한 정점 제거 및 LOD 적용
// 정점 감소 예시 (단순화 알고리즘)
function simplifyGeometry(geometry, targetRatio) {
// 원본 정점 수
var originalVertexCount = geometry.vertexCount;
var targetVertexCount = Math.floor(originalVertexCount * targetRatio);
// 단순화 알고리즘 적용
// ...
// 새 지오메트리 생성
var simplifiedGeometry = new graphicGL.Geometry();
// 단순화된 데이터 설정
// ...
return simplifiedGeometry;
}
- 인덱스 최적화: 정점 캐시 지역성 향상을 위한 인덱스 재배열
// 인덱스 최적화 예시
function optimizeIndices(geometry) {
// 정점 캐시 최적화 알고리즘 적용
// ...
// 최적화된 인덱스 설정
geometry.indices = optimizedIndices;
return geometry;
}
- 정점 속성 최적화: 필요한 속성만 사용하여 메모리 사용량 감소
// 정점 속성 최적화 예시
function optimizeAttributes(geometry, options) {
// 불필요한 속성 제거
if (!options.useNormal) {
geometry.attributes.normal.value = null;
}
if (!options.useTexcoord) {
geometry.attributes.texcoord0.value = null;
}
if (!options.useColor) {
geometry.attributes.color.value = null;
}
// 정밀도 조정 (float -> half float)
if (options.useHalfFloat && renderer.getGLExtension('OES_texture_half_float')) {
// 속성 데이터를 half float로 변환
// ...
}
return geometry;
}
실제 적용 사례
다음은 ECharts-GL 애플리케이션에서 성능 최적화를 적용한 실제 사례입니다:
대규모 산점도 최적화
// 대규모 산점도 최적화 예시
chart.setOption({
series: [{
type: 'scatter3D',
data: largeData,
// 성능 최적화 옵션
progressive: 1000, // 점진적 렌더링
progressiveThreshold: 5000, // 임계값 설정
blendMode: 'lighter', // 블렌딩 모드 최적화
large: true, // 대규모 데이터 모드 활성화
largeThreshold: 2000, // 대규모 데이터 임계값
// 지오메트리 최적화
symbolSize: 3, // 작은 심볼 크기
itemStyle: {
opacity: 0.5 // 낮은 불투명도
}
}]
});
3D 지형 최적화
// 3D 지형 최적화 예시
chart.setOption({
globe: {
// LOD 설정
baseTexture: 'low-res.jpg', // 기본 저해상도 텍스처
heightTexture: 'height-low.jpg', // 저해상도 높이 맵
// 지형 세부 수준 설정
displacementQuality: 'medium', // 중간 품질의 변위 맵
displacementScale: 0.1, // 낮은 변위 스케일
// 셰이딩 최적화
shading: 'color', // 단순한 셰이딩 모드
// 환경 최적화
environment: null, // 환경 맵 비활성화
// 후처리 효과 비활성화
postEffect: {
enable: false
}
}
});
3D 막대 차트 최적화
// 3D 막대 차트 최적화 예시
chart.setOption({
series: [{
type: 'bar3D',
data: data,
// 지오메트리 최적화
bevelSize: 0, // 베벨 효과 비활성화
bevelSmoothness: 0, // 베벨 스무딩 비활성화
// 셰이딩 최적화
shading: 'color', // 단순한 셰이딩 모드
// 조명 최적화
light: {
main: {
shadow: false // 그림자 비활성화
},
ambient: {
intensity: 0.8 // 앰비언트 조명 강화
}
}
}]
});
이러한 최적화 방법을 적용하면 ECharts-GL 애플리케이션의 성능을 크게 향상시킬 수 있습니다. 특히 대규모 데이터 시각화나 복잡한 3D 장면에서 효과적입니다.