2016-03-23 12 views
2

私は、既存のAPIにファイルをアップロードする機能を持つアプリを開発しています。このAPIはJSONオブジェクトのファイルメタデータとコンテンツの両方を取得するので、ファイルのバイナリコンテンツをbase64エンコードされた文字列に変換する必要があります。大規模な配列を処理するときにWebワーカーがメモリ不足になる

これは潜在的に重い操作なので、私はその機能をWebワーカーに移しました。作業者は、バイナリファイルの内容(FileReader.readAsArrayBuffer()から返された)を持つArrayBufferオブジェクトを取り込み、base64でエンコードされた文字列を返します。

これは小さいファイルでは問題ありませんが、私がサポートしなければならない最大のファイル(〜40 MB)では、ワーカー(Internet Explorerでは8007000E)のメモリ不足例外が発生します。まれにそれは通り抜けますが、ほとんどの場合、労働者はちょうど死ぬでしょう。ブラウザのページ全体がクラッシュした(IEとChromeの両方で)場合を除いて、同じことがワーカーに移動する前に同じことが起こりました。 Chromeは、IEよりも労働者のメモリストレスに対して少し弾力性があるようですが、IE(10+)でも正しく動作させる必要があります。

私の労働者:

onmessage = e => { 
    const bytes = new Uint8Array(e.data); 
    const l = bytes.length; 
    const chars = new Array(l); 
    for (let i = 0, j = l - 1; i <= j; ++i, --j) { 
    chars[i] = String.fromCharCode(bytes[i]); 
    chars[j] = String.fromCharCode(bytes[j]); 
    } 
    const byteString = chars.join(''); 
    const base64bytes = btoa(byteString); 

    try { 
    postMessage(base64bytes, [base64bytes]); 
    } catch (e) { 
    postMessage(base64bytes); 
    } 
}; 

が、私はここにいくつかの大きなノー-NOSを作っていますか?メモリ消費を減らす方法はありますか?私が考えた解決策の1つは、ファイル全体ではなくチャンクでコンテンツを処理し、結果の文字列を連結して外部にエンコードすることです。それは実行可能か、それともそれ自身の問題を引き起こすでしょうか?私が知らない他の魔法の機能はありますか?私はFileReader.readAsBinaryString()で希望の光沢がありましたが、現在は標準から削除されています(とにかくIE10でサポートされていない)ので、使用できません。私が考えた

(私はこの質問はあまりにもコードレビューで関連するかもしれないが、私のコードが実際にクラッシュされているので、私はSO正しい場所だった考え出し実現)

+0

ないそれはあなたの問題を解決に関連していますかどうかわからが、なぜ 'chars'を埋めます各端から始まり中央に仕上げますか? –

+0

これは、最適化の試行であったため、反復回数を40MBから20Mに半減します。それはちょっと行ってしまう前に処理できるサイズを増やしましたが、まだ最大のファイルでは不十分です。 –

答えて

0

一つの解決策は次のようになりますコンテンツ全体を処理するのではなく、チャンクでコンテンツを処理し、結果の文字列を連結して外部にエンコードします。それは実行可能か、それともそれ自身の問題を引き起こすでしょうか?

これはhttps://github.com/beatgammit/base64-jsのように、一度に〜16k行っているようです。これを使用して、私のコンピュータで転送可能なもの(IE 10はサポートしていない)を使用しないで、Chromeは190mbのArrayBufferを管理します(これよりも大きいと、無効な文字列の長さが返されます)。IE 11 40mbメモリ不足例外)。

あなたはであり、労働者は、40メガバイトの制限を超えて有望と思われる1つの道を行くために、コード

var exports = {}; 
importScripts('b64.js') 

onmessage = function(e) { 
    var base64Bytes = fromByteArray(new Uint8Array(e.data)); 
    postMessage(base64Bytes); 
}; 

とメインスレッド

var worker = new Worker('worker.js'); 
var length = 1024 * 1024 * 40; 
worker.postMessage(new ArrayBuffer(length)); 

worker.onmessage = function(e) { 
    console.log('Received Base64 in UI thread', e.data.length, 'bytes'); 
} 

を有する場合、https://plnkr.co/edit/SShi1PE4DuMATcyqTRPx?p=previewでこれを見ることができます一度に小さいスライスを作業者に渡し(例えば1MB)、エンコードして結果を戻し、次のスライスをワーカーに渡し、最後にすべての結果を連結します。私はこれを使ってより大きなバッファをエンコードしました(IE 11では250MBまで)。私の疑問は、非同期性によってガベージコレクタが呼び出し間で実行できることです。

例えば

で、https://plnkr.co/edit/un7TXeHwYu8eBltfYAII?p=preview、上記のように労働者で同じコードを持つが、UIスレッドで:

var worker = new Worker('worker.js'); 
var length = 1024 * 1024 * 60; 
var buffer = new ArrayBuffer(length); 

var maxMessageLength = 1024 * 1024; 
var i = 0; 
function next() { 
    var end = Math.min(i + maxMessageLength, length); 
    var copy = buffer.slice(i, end); 
    worker.postMessage(copy); 
    i = end; 
} 

var results = []; 
worker.onmessage = function(e) { 
    results.push(e.data); 
    if (i < length) { 
    next(); 
    } else { 
    results = results.join(''); 
    alert('done ' + results.length); 
    } 
}; 

next(); 
関連する問題