2016-03-23 6 views
2

Ich erstelle eine App, die unter anderem die Möglichkeit bietet, Dateien auf eine vorhandene API hochzuladen. Diese API übernimmt sowohl Metadaten als auch Inhalte von Dateien in einem JSON-Objekt. Daher muss ich den binären Inhalt der Dateien in base64-codierte Strings konvertieren.Web-Mitarbeiter nicht genügend Arbeitsspeicher bei der Verarbeitung großer Arrays

Da dies eine potenziell schwere Operation ist, habe ich die Funktionalität in einen Web-Worker verschoben. Der Worker nimmt ein Objekt ArrayBuffer mit dem binären Dateiinhalt (zurückgegeben von FileReader.readAsArrayBuffer()) auf und gibt eine Base64-codierte Zeichenfolge zurück.

Dies funktioniert gut für kleinere Dateien, aber für die größten Dateien, die ich unterstützen muss (~ 40 MB) führt dies zu Speicherausnahmen für meinen Worker (8007000E in Internet Explorer). In seltenen Fällen passiert es, aber meistens stirbt der Arbeiter. Das gleiche geschah vor dem Verschieben in den Worker, außer dass die gesamte Browserseite abstürzte (sowohl in IE als auch in Chrome). Chrome scheint etwas widerstandsfähiger gegen die Speicherbelastung bei Arbeitern zu sein als IE, aber ich muss es trotzdem im IE (10+) richtig machen.

Mein Arbeiter:

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); 
    } 
}; 

Bin ich einige große No-nos hier machen? Gibt es Möglichkeiten, den Speicherverbrauch zu reduzieren? Eine Lösung, über die ich nachgedacht habe, wäre, den Inhalt in Chunks anstatt der gesamten Datei zu verarbeiten, dann die resultierenden Strings zu verketten und außen zu codieren. Wäre das machbar oder würde das eigene Probleme verursachen? Gibt es noch andere magische Funktionen, von denen ich nichts weiß? Ich hatte einen Hoffnungsschimmer mit FileReader.readAsBinaryString(), aber es ist jetzt aus dem Standard entfernt (und in IE10 sowieso nicht unterstützt), so dass ich es nicht verwenden kann.

(Ich weiß, diese Frage zu im Code Review relevant sein könnte, aber da mein Code tatsächlich abstürzt, ich dachte, so war die richtige Stelle)

+0

Nicht sicher, ob es zu einer Lösung für Ihr Problem verwendet ist, aber warum füllen 'chars' von jedem Ende beginnen und in der Mitte beenden? –

+0

Es halbiert die Anzahl der Iterationen (von 40M bis 20M für eine 40MB-Datei), also war es ein Optimierungsversuch. Es hat die Größe erhöht, die es bewältigen konnte, bevor es ein wenig nach unten ging, aber es ist immer noch nicht genug für die größten Dateien. –

Antwort

0

Eine Lösung, die ich darüber nachgedacht habe wäre Um den Inhalt in Chunks anstatt der gesamten Datei zu verarbeiten, verketten Sie die resultierenden Strings und codieren sie außen. Wäre das machbar oder würde das eigene Probleme verursachen?

Dies ist, was https://github.com/beatgammit/base64-js zu tun scheint, tun ~ 16k auf einmal. Dadurch, dass auf meinem Computer keine Übertragungen verwendet werden (da IE 10 sie nicht unterstützt), kann Chrome einen 190mb ArrayBuffer (größer als dieser beschwert sich über ungültige Stringlänge) und IE 11 40mb (größer als das bekomme ich ein Speicherausnahme).

Sie können dies bei https://plnkr.co/edit/SShi1PE4DuMATcyqTRPx?p=preview sehen, wo der Arbeiter den Code

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

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

und den roten Faden

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'); 
} 

über die 40mb Limit gehen muss, besteht eine Möglichkeit, die vielversprechend scheint, ist zu übergeben Sie dem Worker nur jeweils ein kleineres Segment (sagen wir 1 MB), codieren Sie es, geben Sie das Ergebnis zurück und übergeben Sie erst dann das nächste Segment an den Worker, wobei alle Ergebnisse am Ende verkettet werden. Ich habe es geschafft, dies zu verwenden, um größere Puffer zu kodieren (bis zu 250 MB in IE 11). Mein Verdacht ist, dass die Asynchronität es dem Müllsammler ermöglicht, zwischen Aufrufen zu laufen.

Zum Beispiel bei, https://plnkr.co/edit/un7TXeHwYu8eBltfYAII?p=preview, mit dem gleichen Code in den Arbeitern wie oben, aber in dem UI-Thread:

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();