posts

ECharts-GL 성능 이슈 분석 및 최적화 방안

Oct 1, 2025 updated Oct 1, 2025 3dawsjavascriptlegacyvisualization

목차

  1. 성능 프로파일링
  2. 최적화 방법

성능 프로파일링

WebGL 기반 3D 시각화는 강력한 표현력을 제공하지만, 성능 이슈가 발생할 수 있습니다. ECharts-GL의 성능을 분석하고 최적화하기 위한 프로파일링 방법을 살펴보겠습니다.

WebGL 렌더링 성능 분석

주요 성능 지표

WebGL 렌더링 성능을 측정하는 주요 지표는 다음과 같습니다:

  1. FPS (Frames Per Second): 초당 프레임 수로, 60 FPS 이상이 이상적입니다.
  2. Draw Calls: GPU에 전송되는 렌더링 명령의 수로, 적을수록 좋습니다.
  3. Vertices: 렌더링되는 정점의 수로, 복잡한 장면일수록 증가합니다.
  4. Triangles: 렌더링되는 삼각형의 수로, 3D 모델의 복잡도를 나타냅니다.
  5. Memory Usage: GPU 및 CPU 메모리 사용량으로, 텍스처와 버퍼 크기에 영향을 받습니다.
  6. Shader Complexity: 셰이더 프로그램의 복잡도로, 계산 비용에 영향을 줍니다.

성능 측정 도구

ECharts-GL 애플리케이션의 성능을 측정하기 위한 도구는 다음과 같습니다:

  1. 브라우저 개발자 도구:

    • Chrome DevTools의 Performance 패널
    • Firefox의 Performance 패널
    • Edge의 Performance 패널
  2. WebGL 인스펙터:

  3. ECharts-GL 내장 성능 모니터:

    // 성능 모니터 활성화
    chart.setOption({
        debug: {
            showFPS: true,
            showDrawCallCount: true,
            showTriangleCount: true
        }
    });
    

성능 프로파일링 방법

ECharts-GL 애플리케이션의 성능을 프로파일링하는 단계는 다음과 같습니다:

  1. 기준 성능 측정:

    // 성능 측정 시작
    console.time('Rendering');
    
    // 차트 렌더링
    chart.setOption(option);
    
    // 성능 측정 종료
    console.timeEnd('Rendering');
    
  2. 프레임 타임 측정:

    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);
    
  3. 메모리 사용량 측정:

    // 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 간 통신 오버헤드가 증가합니다.

드로우콜 감소 방법

  1. 배칭(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;
}
  1. 인스턴싱(Instancing): 동일한 기하 데이터를 가진 객체를 효율적으로 렌더링
// 인스턴싱 활성화 예시
function enableInstancing(chart) {
    var seriesModel = chart.getModel().getSeriesByType('scatter3D')[0];
    var data = seriesModel.getData();

    // 인스턴스 속성 설정
    // ...

    // 인스턴싱 활성화
    chart.getZr().__egl.update(chart.getModel(), chart.getZr().painter);
}
  1. 지오메트리 병합: 유사한 지오메트리를 하나로 병합
// 지오메트리 병합 예시
function mergeGeometries(geometries) {
    // 새 지오메트리 생성
    var mergedGeometry = new graphicGL.Geometry();

    // 각 지오메트리의 데이터를 병합
    // ...

    return mergedGeometry;
}

메모리 최적화

WebGL 애플리케이션에서 메모리 관리는 성능에 큰 영향을 미칩니다.

메모리 사용량 감소 방법

  1. 텍스처 최적화: 적절한 크기와 포맷의 텍스처 사용
// 텍스처 최적화 예시
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)));
}
  1. 버퍼 재사용: 버퍼 객체를 재사용하여 가비지 컬렉션 감소
// 버퍼 재사용 예시
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;
    }
};
  1. 지연 로딩: 필요한 리소스만 로드하여 초기 메모리 사용량 감소
// 지연 로딩 예시
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 모델의 지오메트리 최적화는 렌더링 성능 향상에 중요합니다.

지오메트리 최적화 방법

  1. 정점 감소: 불필요한 정점 제거 및 LOD 적용
// 정점 감소 예시 (단순화 알고리즘)
function simplifyGeometry(geometry, targetRatio) {
    // 원본 정점 수
    var originalVertexCount = geometry.vertexCount;
    var targetVertexCount = Math.floor(originalVertexCount * targetRatio);

    // 단순화 알고리즘 적용
    // ...

    // 새 지오메트리 생성
    var simplifiedGeometry = new graphicGL.Geometry();
    // 단순화된 데이터 설정
    // ...

    return simplifiedGeometry;
}
  1. 인덱스 최적화: 정점 캐시 지역성 향상을 위한 인덱스 재배열
// 인덱스 최적화 예시
function optimizeIndices(geometry) {
    // 정점 캐시 최적화 알고리즘 적용
    // ...

    // 최적화된 인덱스 설정
    geometry.indices = optimizedIndices;

    return geometry;
}
  1. 정점 속성 최적화: 필요한 속성만 사용하여 메모리 사용량 감소
// 정점 속성 최적화 예시
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 장면에서 효과적입니다.