webGL 緩解記憶體壓力

undefined發表於2022-03-29

場景問題

最近在做大模型的載入,載入檔案的時候會解析出這個檔案有多少個mesh, line, parameter, 然後都會存在一個變數中去維護這個關係:

const detailedList = {
   mesh: Array,
   line: Array,
   parameter: Array
}

這樣會導致這個記憶體吃緊,畢竟直接存了好幾G的資料資訊。
那還有一種說法是, 檔案載入過來直接就渲染不就行了嗎?
但是這樣會導致DC過多, CPU吃不消。

IndexDB

使用indexDB來充當一個類似記憶體的功能, 我把資料選擇性的存在indexDB中,需要的時候再拿,這樣會大大緩解記憶體壓力。indexDB類似NOSQL,並不是MYSQL這種二維表結構, 取資料的速度是飛快的。

構造一個Store

和大部分前端專案一樣,我也構造了一個store.

enum StoreType {
    MEMORY,
    INDEXDB
}

class Store {
    /**
     * 存放在記憶體中的資料
     */
    private memory: Map<string, any>

    private readonly dbVersion: number;

    /**
     * indexDB
     */
    private db: IDBDatabase;

    private isReady: boolean;

    /**
     * 儲存的value多大才使用indexDB儲存,反之使用map
     */
    private readonly limitForDB: number;

    /**
     * 達到儲存上限後一次性刪除多少條資料
     */
    private readonly deleteCount: number;

    /**
     * 儲存上限
     */
    private readonly upperStorageLimit: number;

    constructor(dbVersion = 1, limitForDB?: number) {
        this.isReady = false
        this.dbVersion = dbVersion
        this.initIndexDB()
        this.memory = new Map()
        this.limitForDB = limitForDB || 30
        this.upperStorageLimit = 10000
        this.deleteCount = 20
    }

    set(key: string, value: any, type?: StoreType) {
        const intoMemory = () => {
            this.memory.set(key, value)
        }

        const intoIndexDB = () => {
            const objectStore = this.getObjectStore();
            objectStore.put({id: key, value}, key);
            this.calculationTimesAndDelete();
        }

        if (typeof type !== 'undefined') {
            type === StoreType.INDEXDB ? intoIndexDB() : intoMemory()
        } else {
            if (Store.sizeOf(value) > this.limitForDB) {
                // indexDB加之前刪除map中存在的key
                this.clear(key, StoreType.MEMORY)
                intoIndexDB()
            } else {
                // 加入map之前刪除indexDB中存在的key
                this.clear(key, StoreType.INDEXDB)
                intoMemory()
            }
        }
    }

    get(key: string, type?: StoreType): Promise<any> {
        return new Promise((resolve, reject) => {
            const fromMemory = (key) => {
                resolve(this.memory.get(key))
            }
            const fromIndexDB = (key) => {
                const objectStore = this.getObjectStore();
                const index = objectStore.index("id");
                const res = index.get(key);
                res.onsuccess = (e) => {
                    // @ts-ignore
                    resolve(e.target.result.value)
                }
                res.onerror = (e) => {
                    reject(e)
                }
            }

            if (typeof type !== 'undefined') {
                type === StoreType.INDEXDB ? fromIndexDB(key) : fromMemory(key)
            } else {
                this.memory.has(key) ? fromMemory(key) : fromIndexDB(key)
            }
        })
    }

    /**
     * 查詢資料庫條數
     */
    calculationTimesAndDelete() {
        const result = this.getObjectStore().count()
        result.onsuccess = (e) => {
            // @ts-ignore
            if (e.result > this.upperStorageLimit) {
                this.deleteFromPrevious()
            }
        }
    }

    /**
     * 達到閾值後刪除最開始建立的資料
     * @description 防止資料膨脹
     */
    deleteFromPrevious() {
        const objectStore = this.getObjectStore()
        const request = objectStore.openCursor();
        let count = 0
        request.onsuccess = (event) => {
            // @ts-ignore
            const cursor = event.target.result
            if (cursor) {
                if (count === this.deleteCount) return;
                count++;
                objectStore.delete(cursor.key)
                cursor.continue()
            }
        }
    }

    getObjectStore() {
        const transaction = this.db.transaction(["store"], "readwrite");
        return transaction.objectStore("store")
    }

    clear(key: string, type?: StoreType) {
        const fromMemoryDel = () => this.memory.delete(key)

        const fromIndexDBDel = () => {
            const objectStore = this.getObjectStore();
            objectStore.delete(key)
        }

        if (typeof type !== 'undefined') {
            type === StoreType.INDEXDB ? fromIndexDBDel() : fromMemoryDel()
        } else {
            this.memory.has(key) ? fromMemoryDel() : fromIndexDBDel()
        }
    }

    initIndexDB() {
        // @ts-ignore
        const indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB
        if (!indexedDB) {
            alert('indexDB被瀏覽器禁止!')
            throw new Error('indexDB forbidden in the browser!')
        }
        const request = indexedDB.open("elephantFiles", this.dbVersion)
        request.onerror = function () {
            throw new Error('Error creating/accessing IndexedDB database');
        }
        request.onsuccess = event => {
            // @ts-ignore
            this.db = event.target.result as IDBDatabase;
            this.isReady = true
        }
        request.onupgradeneeded = (event) => {
            console.log('database upgradeneeded!')
            // @ts-ignore
            const db = event.target.result as IDBDatabase;
            if (!db.objectStoreNames.contains('store')) {
                const store = db.createObjectStore('store')
                store.createIndex('id', 'id')
            }
        }
    }

    /**
     * @description 不確認是否初始化完畢時 將查詢賦值等操作包裹在此
     */
    ready(callback: Function) {
        const step = () => {
            if (this.isReady) {
                callback && callback()
                return;
            }
            window.requestAnimationFrame(step)
        }
        window.requestAnimationFrame(step)
    }

    /**
     * @description 估算位元組大小, 忽略key的長度, 如果是object只計算value, 比JSON.stringify更快
     */
    static sizeOf(value): number {
        const typeSizes = {
            "undefined": () => 0,
            "boolean": () => 4,
            "number": () => 8,
            "string": item => 2 * item.length,
            "object": item => !item ? 0 : Object
                .keys(item)
                .reduce((total, key) => size(key) + size(item[key]) + total, 0)
        };
        const size = value => typeSizes[typeof value](value);
        return size(value)
    }
}


export {StoreType, Store}

使用

const a = new Store();
a.ready(() => {
  a.set('a', { k: 'dsdsdsdsdssdsd', p: [123, 'sdsdsdsdsdsdsdvvvvvvvvv'] })
  a.set('b', 123)

  a.get('b')

  a.get('a')
})

根據客戶的客戶端條件適當調節limitForDBupperStorageLimit

相關文章