freemake como clave activar performance dart data-conversion

performance - clave - como activar freemake video converter



¿Cómo mejorar el rendimiento de Dart de la conversión de datos a/desde binarios? (2)

Es cierto que el rendimiento de los métodos de datos de bytes ( ByteData.setXYZ y ByteData.getXYZ ) es bastante malo en Dart VM en comparación con el acceso directo de matriz de tipo. Comenzamos a trabajar en el tema y los resultados iniciales son prometedores [1].

Mientras tanto, puedes solucionar esta desafortunada regresión de rendimiento al enrollar tu propia conversión a big endian usando arreglos escritos (código completo en [2]):

/// Writer wraps a fixed size Uint8List and writes values into it using /// big-endian byte order. class Writer { /// Output buffer. final Uint8List out; /// Current position within [out]. var position = 0; Writer._create(this.out); factory Writer(size) { final out = new Uint8List(size); if (Endianness.HOST_ENDIAN == Endianness.LITTLE_ENDIAN) { return new _WriterForLEHost._create(out); } else { return new _WriterForBEHost._create(out); } } writeFloat64(double v); } /// Lists used for data convertion (alias each other). final Uint8List _convU8 = new Uint8List(8); final Float32List _convF32 = new Float32List.view(_convU8.buffer); final Float64List _convF64 = new Float64List.view(_convU8.buffer); /// Writer used on little-endian host. class _WriterForLEHost extends Writer { _WriterForLEHost._create(out) : super._create(out); writeFloat64(double v) { _convF64[0] = v; out[position + 7] = _convU8[0]; out[position + 6] = _convU8[1]; out[position + 5] = _convU8[2]; out[position + 4] = _convU8[3]; out[position + 3] = _convU8[4]; out[position + 2] = _convU8[5]; out[position + 1] = _convU8[6]; out[position + 0] = _convU8[7]; position += 8; } }

La evaluación comparativa de esta conversión manual en su prueba arroja una mejora de aproximadamente 6x:

import ''dart:typed_data''; import ''package:benchmark_harness/benchmark_harness.dart''; import ''writer.dart''; class ConversionBenchmarkManual extends BenchmarkBase { Uint8List result; ConversionBenchmarkManual() : super("Conversion (MANUAL)"); // The benchmark code. void run() { const int BufSize = 262144; // 256kBytes const int SetSize = 64; // one "typical" set of data, gets repeated final w = new Writer(BufSize); double doubleContent = 0.0; // used to simulate double content int intContent = 0; // used to simulate int content int offset = 0; for (int j = 0; j < (BufSize / SetSize); j++) { // The following represents some "typical" conversion mix: w.writeFloat64(doubleContent); doubleContent += 0.123; for (int k = 0; k < 8; k++) { // main use case w.writeFloat32(doubleContent); doubleContent += 0.123; } w.writeInt32(intContent); intContent++; w.writeInt32(intContent); intContent++; w.writeInt16(intContent); intContent++; w.writeInt16(intContent); intContent++; w.writeInt8(intContent); intContent++; w.writeInt8(intContent); intContent++; w.writeString("AsciiStrng"); assert((offset % SetSize) == 0); // ensure the example content fits [SetSize] bytes } result = w.out; // only this can be used for further processing } }

[1] code.google.com/p/dart/issues/detail?id=22107

[2] https://gist.github.com/mraleph/4eb5ccbb38904075141e

Trabajo de consultoría para compañías alemanas más grandes. Future Technologies Group He transferido alrededor de 6000 líneas de software de servidor de Java a Dart. Esto debería ayudar a responder la pregunta de si Dart se puede usar de manera eficiente en el servidor. (Lo que en sí mismo daría luz verde a Dart debido a la ventaja buscada de tener un idioma para la programación del lado del cliente y del servidor).

Aprender sobre Dart (con el que disfruté mucho trabajando) me dio una expectativa de una penalización de rendimiento del 30-50% en relación con Java, pero en cualquier caso no es peor que el 100% (dos veces más lento), que es el límite para el proceso de decisión mencionado. encima.

El puerto se fue sin problemas. Aprendí mucho. Las pruebas unitarias estaban bien. Pero el rendimiento resultó ser extremadamente malo ... SIETE veces más lento en general en comparación con el programa Java.

El perfil del código reveló dos causas principales: la conversión de datos y la E / S de archivos. Tal vez estoy haciendo algo mal? Antes de volver a mi cliente y cancelar su investigación de Dart, me gustaría buscar algunos consejos sobre cómo mejorar las cosas. Comencemos con la conversión de datos, la conversión de tipos de datos nativos de Dart en varios formatos binarios que se pueden usar para una transferencia y almacenamiento de datos efectivos.

Por lo general, estas conversiones son simples y muy rápidas, ya que nada tiene que convertirse realmente desde el formato interno usado, sino que se almacena principalmente en un búfer. Creé un programa de referencia que de alguna manera refleja el uso típico de estas conversiones en mi programa:

import ''dart:typed_data''; import ''package:benchmark_harness/benchmark_harness.dart''; // Create a new benchmark by extending BenchmarkBase class ConversionBenchmark extends BenchmarkBase { Uint8List result; ConversionBenchmark() : super("Conversion"); // The benchmark code. void run() { const int BufSize = 262144; // 256kBytes const int SetSize = 64; // one "typical" set of data, gets repeated ByteData buffer = new ByteData(BufSize); double doubleContent = 0.0; // used to simulate double content int intContent = 0; // used to simulate int content int offset = 0; for (int j = 0; j < buffer.lengthInBytes / SetSize; j++) { // The following represents some "typical" conversion mix: buffer.setFloat64(offset, doubleContent); offset += 8; doubleContent += 0.123; for (int k = 0; k < 8; k++) { // main use case buffer.setFloat32(offset, doubleContent); offset += 4; doubleContent += 0.123; } buffer.setInt32(offset, intContent); offset += 4; intContent++; buffer.setInt32(offset, intContent); offset += 4; intContent++; buffer.setInt16(offset, intContent); offset += 2; intContent++; buffer.setInt16(offset, intContent); offset += 2; intContent++; buffer.setInt8(offset, intContent); offset += 1; intContent++; buffer.setInt8(offset, intContent); offset += 1; intContent++; buffer.buffer.asUint8List(offset).setAll(0, "AsciiStrng".codeUnits); offset += 10; // [ByteData] knows no other mechanism to transfer ASCII strings in assert((offset % SetSize) == 0); // ensure the example content fits [SetSize] bytes } result = buffer.buffer.asUint8List(); // only this can be used for further processing } } main() { new ConversionBenchmark().report(); }

Se basa en el arnés de referencia de https://github.com/dart-lang/benchmark_harness . Para comparaciones, utilicé el siguiente programa Java basado en un puerto del arnés de referencia de Dart de https://github.com/bono8106/benchmark_harness_java :

package ylib.tools; import java.nio.ByteBuffer; public class ConversionBenchmark extends BenchmarkBase { public ByteBuffer result; public ConversionBenchmark() { super("Conversion"); } // The benchmark code. @Override protected void run() { final int BufSize = 262144; // 256kBytes final int SetSize = 64; // one "typical" set of data, gets repeated ByteBuffer buffer = ByteBuffer.allocate(BufSize); double doubleContent = 0.0; // used to simulate double content int intContent = 0; // used to simulate int content for (int j = 0; j < (buffer.capacity() / SetSize); j++) { // The following represents some "typical" conversion mix: buffer.putDouble(doubleContent); doubleContent += 0.123; for (int k = 0; k < 8; k++) { // main use case buffer.putFloat((float)doubleContent); doubleContent += 0.123; } buffer.putInt(intContent); intContent++; buffer.putInt(intContent); intContent++; buffer.putShort((short)intContent); intContent++; buffer.putShort((short)intContent); intContent++; buffer.put((byte)intContent); intContent++; buffer.put((byte)intContent); intContent++; buffer.put("AsciiStrng".getBytes()); //assert((buffer.position() % SetSize) == 0); // ensure the example content fits [SetSize] bytes } buffer.flip(); // needed for further processing result = buffer; // to avoid the compiler optimizing away everything } public static void main(String[] args) { new ConversionBenchmark().report(); } }

El código Java se ejecuta casi exactamente 10 veces más rápido que el código Dart en mi máquina Intel Windows 7. Ambos se ejecutan en modo de producción en sus máquinas virtuales respectivas.

¿Hay un error obvio en el código? ¿O hay diferentes clases de Dart disponibles para hacer el trabajo? ¿Alguna explicación de por qué Dart es mucho más lento con estas simples conversiones? ¿O tengo expectativas completamente equivocadas con respecto al rendimiento de Dart VM?


Quiero agregar algunos detalles sobre cómo finalmente resolví el problema de rendimiento y cómo se ven los resultados.

Primero utilicé el enfoque postet de Vyacheslav Egorov y desarrollé a partir de él mi propia clase de convertidor de datos que proporciona conversiones en ambas direcciones. Todavía no es un código de producción, pero funcionó muy bien para mi puerto de software de servidor y, por lo tanto, lo adjunto a continuación. Mantuve intencionalmente el [buffer] una variable pública. Esto podría no hacer una encapsulación perfecta, pero permite escribir y leer directamente desde el búfer, por ejemplo, a través de un [RandomAccessFile.readInto] y [RandomAccessFile.writeFrom]. Todo sencillo y eficiente!

Realmente resultó que estas conversiones de datos eran el culpable del lento rendimiento inicial siete veces más lento que la versión de Java. Con el cambio la brecha de rendimiento se redujo considerablemente. La versión Dart de la aplicación del servidor de 6000 líneas ahora sigue la versión de Java en casi un 30%. Mejor de lo que esperaba de un lenguaje con un concepto de escritura tan flexible. Eso dejará a Dart en una buena posición para las futuras decisiones tecnológicas de mis clientes.

En mi opinión, tener un idioma para aplicaciones de cliente y servidor podría ser un muy buen argumento para Dart.

Y aquí viene el código para el convertidor de datos como se usa para este proyecto:

part of ylib; /// [DataConverter] wraps a fixed size [Uint8List] and converts values from and into it /// using big-endian byte order. /// abstract class DataConverter { /// Buffer. final Uint8List buffer; /// Current position within [buffer]. int _position = 0; DataConverter._create(this.buffer); /// Creates the converter with its associated [buffer]. /// factory DataConverter(size) { final out = new Uint8List(size); if (Endianness.HOST_ENDIAN == Endianness.LITTLE_ENDIAN) { return new _ConverterForLEHost._create(out); } else { return new _ConverterForBEHost._create(out); } } int get length => buffer.length; int get position => _position; set position(int position) { if ((position < 0) || (position > buffer.lengthInBytes)) throw new ArgumentError(position); _position = position; } double getFloat64(); putFloat64(double v); double getFloat32(); putFloat32(double v); static const int _MaxSignedInt64plus1 = 9223372036854775808; static const int _MaxSignedInt32plus1 = 2147483648; static const int _MaxSignedInt16plus1 = 32768; static const int _MaxSignedInt8plus1 = 128; int getInt64() { int v = buffer[_position + 7] | (buffer[_position + 6] << 8) | (buffer[_position + 5] << 16) | (buffer[_position + 4] << 24) | (buffer[_position + 3] << 32) | (buffer[_position + 2] << 40) | (buffer[_position + 1] << 48) | (buffer[_position] << 56); _position += 8; if (v >= _MaxSignedInt64plus1) v -= 2 * _MaxSignedInt64plus1; return v; } putInt64(int v) { assert((v < _MaxSignedInt64plus1) && (v >= -_MaxSignedInt64plus1)); buffer[_position + 7] = v; buffer[_position + 6] = (v >> 8); buffer[_position + 5] = (v >> 16); buffer[_position + 4] = (v >> 24); buffer[_position + 3] = (v >> 32); buffer[_position + 2] = (v >> 40); buffer[_position + 1] = (v >> 48); buffer[_position + 0] = (v >> 56); _position += 8; } int getInt32() { int v = buffer[_position + 3] | (buffer[_position + 2] << 8) | (buffer[_position + 1] << 16) | (buffer[_position] << 24); _position += 4; if (v >= _MaxSignedInt32plus1) v -= 2 * _MaxSignedInt32plus1; return v; } putInt32(int v) { assert((v < _MaxSignedInt32plus1) && (v >= -_MaxSignedInt32plus1)); buffer[_position + 3] = v; buffer[_position + 2] = (v >> 8); buffer[_position + 1] = (v >> 16); buffer[_position + 0] = (v >> 24); _position += 4; } // The following code which uses the ''double'' conversion methods works but is about 50% slower! // // final Int32List _convI32 = new Int32List.view(_convU8.buffer); // // int getInt32() { // _convU8[0] = out[_position + 0]; _convU8[1] = out[_position + 1]; // _convU8[2] = out[_position + 2]; _convU8[3] = out[_position + 3]; // _position += 4; // return _convI32[0]; // } // // putInt32(int v) { // _convI32[0] = v; // out[_position + 0] = _convU8[0]; out[_position + 1] = _convU8[1]; // out[_position + 2] = _convU8[2]; out[_position + 3] = _convU8[3]; // _position += 4; // } int getInt16() { int v = buffer[_position + 1] | (buffer[_position] << 8); _position += 2; if (v >= _MaxSignedInt16plus1) v -= 2 * _MaxSignedInt16plus1; return v; } putInt16(int v) { assert((v < _MaxSignedInt16plus1) && (v >= -_MaxSignedInt16plus1)); buffer[_position + 1] = v; buffer[_position + 0] = (v >> 8); _position += 2; } int getInt8() { int v = buffer[_position++]; if (v >= _MaxSignedInt8plus1) v -= 2 * _MaxSignedInt8plus1; return v; } putInt8(int v) { assert((v < _MaxSignedInt8plus1) && (v >= -_MaxSignedInt8plus1)); buffer[_position] = v; _position++; } String getString(int length) { String s = new String.fromCharCodes(buffer, _position, _position + length); _position += length; return s; } putString(String str) { buffer.setAll(_position, str.codeUnits); _position += str.codeUnits.length; } } /// Lists used for data convertion (alias each other). final Uint8List _convU8 = new Uint8List(8); final Float32List _convF32 = new Float32List.view(_convU8.buffer); final Float64List _convF64 = new Float64List.view(_convU8.buffer); /// Writer used on little-endian host. class _ConverterForLEHost extends DataConverter { _ConverterForLEHost._create(out) : super._create(out); double getFloat64() { _convU8[0] = buffer[_position + 7]; _convU8[1] = buffer[_position + 6]; _convU8[2] = buffer[_position + 5]; _convU8[3] = buffer[_position + 4]; _convU8[4] = buffer[_position + 3]; _convU8[5] = buffer[_position + 2]; _convU8[6] = buffer[_position + 1]; _convU8[7] = buffer[_position + 0]; _position += 8; return _convF64[0]; } putFloat64(double v) { _convF64[0] = v; buffer[_position + 7] = _convU8[0]; buffer[_position + 6] = _convU8[1]; buffer[_position + 5] = _convU8[2]; buffer[_position + 4] = _convU8[3]; buffer[_position + 3] = _convU8[4]; buffer[_position + 2] = _convU8[5]; buffer[_position + 1] = _convU8[6]; buffer[_position + 0] = _convU8[7]; _position += 8; } double getFloat32() { _convU8[0] = buffer[_position + 3]; _convU8[1] = buffer[_position + 2]; _convU8[2] = buffer[_position + 1]; _convU8[3] = buffer[_position + 0]; _position += 4; return _convF32[0]; } putFloat32(double v) { _convF32[0] = v; assert(_convF32[0].isFinite || !v.isFinite); // overflow check buffer[_position + 3] = _convU8[0]; buffer[_position + 2] = _convU8[1]; buffer[_position + 1] = _convU8[2]; buffer[_position + 0] = _convU8[3]; _position += 4; } } /// Writer used on the big-endian host. class _ConverterForBEHost extends DataConverter { _ConverterForBEHost._create(out) : super._create(out); double getFloat64() { _convU8[0] = buffer[_position + 0]; _convU8[1] = buffer[_position + 1]; _convU8[2] = buffer[_position + 2]; _convU8[3] = buffer[_position + 3]; _convU8[4] = buffer[_position + 4]; _convU8[5] = buffer[_position + 5]; _convU8[6] = buffer[_position + 6]; _convU8[7] = buffer[_position + 7]; _position += 8; return _convF64[0]; } putFloat64(double v) { _convF64[0] = v; buffer[_position + 0] = _convU8[0]; buffer[_position + 1] = _convU8[1]; buffer[_position + 2] = _convU8[2]; buffer[_position + 3] = _convU8[3]; buffer[_position + 4] = _convU8[4]; buffer[_position + 5] = _convU8[5]; buffer[_position + 6] = _convU8[6]; buffer[_position + 7] = _convU8[7]; _position += 8; } double getFloat32() { _convU8[0] = buffer[_position + 0]; _convU8[1] = buffer[_position + 1]; _convU8[2] = buffer[_position + 2]; _convU8[3] = buffer[_position + 3]; _position += 4; return _convF32[0]; } putFloat32(double v) { _convF32[0] = v; assert(_convF32[0].isFinite || !v.isFinite); // overflow check buffer[_position + 0] = _convU8[0]; buffer[_position + 1] = _convU8[1]; buffer[_position + 2] = _convU8[2]; buffer[_position + 3] = _convU8[3]; _position += 4; } }

Y una unidad de prueba muy pequeña y básica:

import ''package:ylib/ylib.dart''; import ''package:unittest/unittest.dart''; // -------- Test program for [DataConverter]: -------- void main() { DataConverter dc = new DataConverter(100); test(''Float64'', () { double d1 = 1.246e370, d2 = -0.0000745687436849437; dc.position = 0; dc..putFloat64(d1)..putFloat64(d2); dc.position = 0; // reset it expect(dc.getFloat64(), d1); expect(dc.getFloat64(), d2); }); test(''Float32'', () { double d1 = -0.43478e32, d2 = -0.0; dc.position = 0; dc..putFloat32(d1)..putFloat32(d2); dc.position = 0; // reset it expect(dc.getFloat32(), closeTo(d1, 1.7e24)); expect(dc.getFloat32(), d2); }); test(''Int64'', () { int i1 = 9223372036854775807, i2 = -22337203685477580; dc.position = 3; dc..putInt64(i1)..putInt64(i2); dc.position = 3; // reset it expect(dc.getInt64(), i1); expect(dc.getInt64(), i2); }); test(''Int32_16_8'', () { int i1 = 192233720, i2 = -7233, i3 = 32, i4 = -17; dc.position = 0; dc..putInt32(i1)..putInt16(i2)..putInt8(i3)..putInt32(i4); dc.position = 0; // reset it expect(dc.getInt32(), i1); expect(dc.getInt16(), i2); expect(dc.getInt8(), i3); expect(dc.getInt32(), i4); }); test(''String'', () { String s1 = r"922337203!§$%&()=?68547//75807", s2 = "-22337203685477580Anton"; int i1 = -33; dc.position = 33; dc..putString(s1)..putInt8(i1)..putString(s2); dc.position = 33; // reset it expect(dc.getString(s1.length), s1); expect(dc.getInt8(), i1); expect(dc.getString(s2.length), s2); }); }