dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.05k stars 1.55k forks source link

Design and implement Dart VM FFI #34452

Closed mraleph closed 3 years ago

mraleph commented 5 years ago

The present feature tracks the implementation of Dart FFI support enabling interop with C & C++ code from Dart.

Status

The feature will be in stable as of Dart 2.12. More details here: https://dart.dev/guides/libraries/c-interop

For any discussion of this feature, or feedback or questions regarding the feature, kindly join and post the dart-ffi group: https://groups.google.com/forum/#!forum/dart-ffi

We will still actively adding new features to dart:ffi. Please refer to https://github.com/dart-lang/sdk/labels/library-ffi for open issues and feature requests.

Background

Some inspirational references:

sjindel-google commented 5 years ago

What version of the Dart SDK is your VSCode install using, and what plugins are you using?

kingwill101 commented 5 years ago

totally forgot another version installed system wide, so vscode may be using that one

kingwill101@kingtech:~/go/src/gitlab.com/kingwill101/test/dart$ dart --version
Dart VM version: 2.2.0 (Unknown timestamp) on "linux_x64"
sjindel-google commented 5 years ago

The FFI Preview is not available on that version of Dart; you'll need use top-of-tree or wait for Dart 2.3.

Kleak commented 5 years ago

Hi,

First of all thanks to bring such functionality in Dart it’s really awesome and a long awaited feature. We come after some testing of the ffi on the libsodium and here are what we gather.

First of all starting is not really hard with the Sqlite example and we can quickly wrap some key function of libsodium for benchmark purpose. Here is our repos https://github.com/santetis/libsodium.dart. We have wrap the initialization of libsodium, sha256 and sha512 to benchmark them against the crypto package (only support sha256) and pointycastle package (which support both). And here are the result :

Really quick to have something working with ffi (something like ~2/3h to have the 3 functions) ffi is really slow (up to 10 time slower) (in fact it’s not really ffi but the conversion between type)

Here are the result of the benchmark we run (you can see the code in the benchmark folder).

For the crypto package :

master $ dart crypto/main.dart 
CryptoSHA256: FooBar
CryptoSHA256: 0d749abe1377573493e0df74df8d1282e46967754a1ebc7cc6323923a788ad5c
CryptoSHA256(RunTime): 16.493551047336304 us.

For the pointycastle package :

master $ dart pointycastle/main.dart 
PointyCastleSHA256: FooBar
PointyCastleSHA256: 0d749abe1377573493e0df74df8d1282e46967754a1ebc7cc6323923a788ad5c
PointyCastleSHA256(RunTime): 15.91678736848807 us.
PointyCastleSHA512: FooBar
PointyCastleSHA512: 0d6fc00052fee1d3ae1880d5dc59b74dd50449ffdea399449223d2c5b792395ce64153b150fc0fc01bfeed30c7347411cfb8a3b17b51fd8aa6c03acfbcd09e7b
PointyCastleSHA512(RunTime): 70.59812919166961 us.

For the libsodium package :

master $ dart libsodium/main.dart 
LibSodiumSHA256: FooBar
LibSodiumSHA256: 0d749abe1377573493e0df74df8d1282e46967754a1ebc7cc6323923a788ad5c
LibSodiumSHA256(RunTime): 172.85213032581453 us.
LibSodiumSHA512: FooBar
LibSodiumSHA512: 0d6fc00052fee1d3ae1880d5dc59b74dd50449ffdea399449223d2c5b792395ce64153b150fc0fc01bfeed30c7347411cfb8a3b17b51fd8aa6c03acfbcd09e7b
LibSodiumSHA512(RunTime): 285.42451484018267 us.
LibSodiumSHA512FFIOnly: FooBar
LibSodiumSHA512FFIOnly: Pointer<Uint8>: address=0x55b2ebc8fe80
LibSodiumSHA512FFIOnly(RunTime): 7.8313598346020115 us.

How to read this result :

LibSodiumSHA512 is the benchmark for using a function that can be used in every dart program no ffi type is in the function signature

LibSodiumSHA512FFIOnly is the benchmark with only the ffi part. Here you will need to pass a Pointer<T> to the function and the result you get is also a Pointer<T>

What can we extract from this result ?

ffi seems super quick but we need to transform the data to send it and receive it since the Pointer<T> object is not really useful in dart. Here we are converting String to Pointer<Uint8> that it seems it’s the part that is taking all the time.

So some question now :

@mit-mit

mraleph commented 5 years ago

@Kleak Thanks for trying FFI out, Kevin!

This was not explicitly highlighted in @mit-mit's invitation - but we are well aware that performance of FFI is lacking - especially when it comes to workloads which are reading and writing a lot of data through Pointer<T> on the Dart side. This is however will be fixes in the MVP and Release versions of FFI.

What we are looking for is feedback on API design - rather than performance.

To put this into perspective a line of code like this:

      ptr.elementAt(i).store(codeUnits[i]);

currently translates into two calls into runtime system and an allocation - which means CPU has to chew through several hundred CPU instructions (if not more). Once all planned optimizations are implemented this like of code should translate into just a few CPU instructions.

If not is there some plan to automatically marshall built-in Dart type in a way that is faster than what we can do now.

We do plan to provide some builtin ways to marshal common data types like strings and arrays.

I think one of the obvious conclusions we can make here is that CString-like class should be provided by FFI code (either dart:ffi or maybe package:ffi), rather than implemented by all FFI clients again and again.

Kleak commented 5 years ago

Thanks for all the explanation @mraleph . For the API design in found it pretty intuitive. And except that yes provide basic type in FFI code i found it's fine. I haven't test all the capabilities so this is based on what i have seen. Happy to help on the CString-like class i think it's better to have a package for this kind of thing, like that it can evolve more quickly on a solid base provided by the dart sdk.

I will continue to explore more in the coming week.

Kleak commented 5 years ago

@mraleph one more question How to distribute the .so/.dylib/.dll ? Should we let the user deliver this part or should we distribute it ? I think this part should be covered in the example because it's really not obvious.

ds84182 commented 5 years ago

@mraleph haven't had a chance to experiment with FFI yet, but are there plans to have an easy way to bridge external allocations to typed data? Like maybe a way to create External TypedData from a pointer+length pair?

I have a Rust library that takes large images and streams in fixed-sized chunks of raw image data. I need to turn these into typed data (hopefully without unnecessary copies) so I can pass it into dart:ui's decodeImageFromPixels function, which takes a Uint8List.

It would also be helpful to have zero-copy String and non-external TypedData handling, dunno if that's possible since Dart moves heap objects around during GC.

dcharkes commented 5 years ago

@Kleak We are aware of the distribution question, but we do not have an answer yet.

Is there a plan for distributing packages that use the new FFI, i.e. on Pub?

@thosakwe I've discussed this @jonasfj, but at this point we don't know how yet we want to approach this. Should we distribute source or binaries? (Or provide the option to do either?) It probably depends on the use case. And if the package is used within Flutter, can we reuse something that Flutter does? We're aware of these questions, but do not have an answer yet.

mraleph commented 5 years ago

@ds84182 yes, supporting marshalling of arrays and strings as external types is one the list of items we would totally want to support in FFI. I am not sure if we have a bug filed for this (/cc @dcharkes @sjindel-google) but it was definitely part of the original vision.

It would also be helpful to have zero-copy String and non-external TypedData handling, dunno if that's possible since Dart moves heap objects around during GC.

This is possible if you can guarantee that GC can't occur, e.g. if native method is declared as a leaf (@sjindel-google just filed a bug to support those https://github.com/dart-lang/sdk/issues/36707).

sjindel-google commented 5 years ago

The support for strings and arrays can be built as a library on top of the API available now, but these should probably come included with dart:ffi. I've filed https://github.com/dart-lang/sdk/issues/36711

sjindel-google commented 5 years ago

https://github.com/dart-lang/sdk/issues/36247 is tracking FFI performance. Other benchmarks are welcome.

Kleak commented 5 years ago

Is there an example to create a flutter application with ffi ? Especially the part where to put the .so file.

sjindel-google commented 5 years ago

@Kleak Please check out samples/ffi/sqlite/doc/android.md in the repo.

Kleak commented 5 years ago

@mraleph @sjindel-google One little problem i'm facing right now is that in libsodium some part of the lib are available only on some platform. So i ended up writing some generic code to add the capabilities to check is the function is available before running it. Here is the generic code :

abstract class Bindings<DartSignature extends Function, NativeSignature> {
  final String functionName;

  DartSignature f;

  Bindings(DynamicLibrary sodium, this.functionName) {
    try {
      f = sodium
          .lookup<NativeFunction<NativeSodiumInitSignature>>(functionName)
          .asFunction<DartSignature>();
    } catch (_) {}
  }

  bool get isAvailable => f != null;
}

and here an example of how to use it :

class SodiumInitBindings
    extends Bindings<SodiumInitSignature, NativeSodiumInitSignature> {
  SodiumInitBindings(DynamicLibrary sodium) : super(sodium, 'sodium_init');

  int call() {
    if (!isAvailable) {
      throw BindingIsNotAvailableException(
          '$functionName bindings is not available on ${Platform.operatingSystem}');
    }
    return f();
  }
}

where SodiumInitSignature correspond to typedef int SodiumInitSignature(); and NativeSodiumInitSignature correspond to typedef Int32 NativeSodiumInitSignature();

the problem is when i run this code i got

lib/src/bindings/bindings.dart:12:12: Error: Expected type 'DartSignature' to be 'int Function()', which is the Dart type corresponding to 'NativeFunction<Int32 Function()>'.
 - 'NativeFunction' is from 'dart:ffi'.
 - 'Int32' is from 'dart:ffi'.
          .asFunction<DartSignature>();
           ^

So my question is : Is that normal behavior ? because I found it really annoying :/

rootext commented 5 years ago

Could you please add ability to create Pointer from TypedData or ByteBuffer or List? It should neither allocate memory nor copy data operation.

It is "must have" feature to use Dart FFI with audio, video and grapchic libraries.

mraleph commented 5 years ago

@rootext Note that in general creating and using interior pointers into Dart objects is not safe, because GC is managing these objects (deleting them and moving them around) - so if you need a pointer that is valid for a longer period of time then you can't just pass around pointer into a typed data object. If GC happens the pointer might become invalid.

rootext commented 5 years ago

@mraleph, I am going to use Dart FFI for OpenGL API. There are few functions calls where I need to send pointer to raw byte buffer: glShaderSource, glBufferData, glTexImage2D. What is the recomended way for this?

mraleph commented 5 years ago

@rootext you will have to allocate raw native memory, then fill it in on the Dart side then send it. There will be a way to make typed data object from raw native memory to make actual filling more ergonomic.

rootext commented 5 years ago

@mraleph, Great. Is it for 2.3 or future version?

mraleph commented 5 years ago

@rootext future. FFI is not part of 2.3 anyway - it's in (super early stage) preview.

mraleph commented 5 years ago

@Kleak sorry for the unhelpful error message - but the code you are writing is not expected to work. We currently require that type arguments arguments to lookup and asFunction are compile time constants. The reason for that is AOT: in AOT we need to know what kind of trampolines to generate for going between Dart and native code. These trampolines would be the most efficient when their Dart and native signatures is statically known.

Kleak commented 5 years ago

But this definition is known at compile time no ?

class SodiumInitBindings
    extends Bindings<SodiumInitSignature, NativeSodiumInitSignature> {
  SodiumInitBindings(DynamicLibrary sodium) : super(sodium, 'sodium_init');

  int call() {
    if (!isAvailable) {
      throw BindingIsNotAvailableException(
          '$functionName bindings is not available on ${Platform.operatingSystem}');
    }
    return f();
  }
}

all the generic are known at compile time and are constant in the subclass

EDIT: is there a way to mimic that or do i have to write that every time ? is this a limitation of the preview or this will stay like this (because it's something valid in dart) ?

mraleph commented 5 years ago

@Kleak it would be know in compile time if Dart generics worked like C++ templates, which are instantiated by expansion (essentially cloning them with type parameters replaced with actual types), but they don't. When you write

abstract class Bindings<DartSignature extends Function, NativeSignature> {
  final String functionName;

  Bindings(DynamicLibrary sodium, this.functionName) {
    try {
      f = sodium
          .lookup<NativeFunction<NativeSodiumInitSignature>>(functionName)
          .asFunction<DartSignature>();
    } catch (_) {}
  }
}

that more or less means (this is not a valid syntax - but that's how it works)

abstract class Bindings {
  final List<Type> :typeArguments; // secret field that contains type arguments 

  Bindings(DynamicLibrary sodium, this.functionName) {
    try {
      f = sodium
          .lookup<NativeFunction<this.$typeArguments[0]>>(functionName)
          .asFunction<this.$typeArguments[1]>();
    } catch (_) {}
  }
}

is there a way to mimic that or do i have to write that every time? is this a limitation of the preview or this will stay like this (because it's something valid in dart)?

I don't think there is a way to mimic what you want - your best bet is just to generate boilerplate code using some helper script.

This limitation is somewhat intrinsic to how things are designed and how Dart generics work. So I am not too optimistic that it would change - however if we discover that this comes up often, then we need to go back to the drawing board.

[As I mentioned above - this limitation originates from the desire to make things efficient in AOT. Maybe we can make a special annotation to force some generics expand at compile time in AOT.]

kingwill101 commented 5 years ago

@mraleph is there an example that shows how to map something like const char * SomeFunction() to a dart function ?

rootext commented 5 years ago

@glenfordwilliams, you can use this class from sdk tests https://github.com/dart-lang/sdk/blob/master/tests/ffi/cstring.dart typedef SomeFunction = CString Function();

kingwill101 commented 5 years ago

@rootext tried that earlier but had issues with it. I'm trying to integrate a lib built using cgo which generates the following for strings

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif

tried using

@ffi.Struct()
class GoString {
  CString p;
}

but that doesn't work

mraleph commented 5 years ago

@glenfordwilliams do I understand correctly that you have a function that returns _GoString_ rather than const char*, e.g. _GoString_ SomeFunction().

Currently binding such functions is not supported. (See https://github.com/dart-lang/sdk/issues/36730)

However if you have a function that returns a pointer to _GoString_ like _GoString_* SomeFunction() - then you should be able to bind it.

kingwill101 commented 5 years ago

@mraleph yes that's pretty much it

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif

extern GString GetString();
nhachicha commented 5 years ago

Hi, thanks for shipping this preview, I wrote a simple prototype using Realm https://github.com/nhachicha/realm-dart-ffi

some feedback:

sjindel-google commented 5 years ago

Thanks for the feedback. The first three of suggestions we are very aware of and are on our roadmap. I'm curious about the last two though:

Sometimes the C/C++ needs to return an array of primitives (as opposed to a pointer), currently accessing each element via an FFI call is suboptimal for large arrays... is there any similar approach to JNI where we can access a Dart array backed by the same memory or allow some kind of move semantics internally which transfer the array element to Dart without the need for copying/marshalling.

I'm not sure what you mean by "return an array of primitives (as opposed to a pointer)". What's problematic about returning a pointer to a heap-allocated array and access it via Pointer.load and Pointer.store?

Once we have callbacks from C/C++, it is unclear how the pthreads will interact with Dart?, will the callback invoke in an Isolates or in the Event Loop? for Realm there are still some internal resources which are thread confined, using some resources allocated in a callback in one thread then accessing them from another could be problematic.

In the Dart VM, each thread can be inside of at most one isolate at a time (newly created threads are not inside any isolate by default). Within each isolate, only one thread in that isolate can execute Dart code (called the "mutator" thread).

We have two plans for callbacks: synchronous callbacks and asynchronous callbacks.

A synchronous callback must be invoked on the mutator thread of the isolate it was taken from (for example, it can be invoked by C code which in turn was called by Dart via an FFI call). The VM will abort if it's called on the wrong thread.

An asynchronous callback will add a task to the microtask queue which will eventually invoke the callback on the mutator thread. An thread inside the appropriate isolate can invoke an asynchronous callback, but will block until the microtask is processed by the mutator thread.

How does this work for Realm?

t-artikov commented 5 years ago

@sjindel-google Asynchronous callbacks are definitely a must-have feature. Could you please show how it will work on an example?

Suppose we have a C++ function

using Input = std::string;
using Result = std::string;
void doHeavyTextProcessing(Input input, std::function<void(Result)> onReady);

which makes computations on an internal thread and invokes the callback on it.

What code should be written to make it available from Dart as

Future<String> doHeavyTextProcessing(String input);

?

ds84182 commented 5 years ago

Here's some thoughts on the current FFI api:

I somewhat dislike the current way of specifying native functions... It's great for unstructured testing, but I feel that it somewhat falls apart when you're trying to specify a large mass of functions.

Something like this would be nicer:

abstract class Kernel32 extends Library {
  bool QueryPerformanceCounter(Pointer<Uint64> out);
  bool QueryPerformanceFrequency(Pointer<Uint64> out);
}

abstract class FooBar extends Library {
  @Int32
  int Truncate64(@Int64 int x);
}

// Part of dart:ffi
class DynamicLibrary {
  // ...
  T asLibrary<T extends Library>();
}

It would make specifying and organization a bit easier.

Another thing is that bools seem to be unsupported?

Also analyzer lints would be awesome! A lot of issues I discover with my FFI code is something that could've been caught by the analyzer instead of the frontend.

dcharkes commented 5 years ago

@ds84182 These are some great suggestions!

We have to specify both the Dart signature and C signature, or the FFI does not know how to marshal between the Dart and C representation of values passed to functions. This encodes both those signatures:

abstract class FooBar extends Library {
  @Int32
  int Truncate64(@Int64 int x);
}

This we could maybe get to work, tracking in https://github.com/dart-lang/sdk/issues/36854.

Another thing is that bools seem to be unsupported?

Yes, we use Uint8 for those. Though I see that that requires one to convert between a bool and number on the Dart side. We could add a Bool C type that is treated as uint8_t and corresponds to the bool Dart type. Discussing in https://github.com/dart-lang/sdk/issues/36855.

Also analyzer lints would be awesome! A lot of issues I discover with my FFI code is something that could've been caught by the analyzer instead of the frontend.

Already on our todo list: https://github.com/dart-lang/sdk/issues/35777.

mraleph commented 5 years ago

I have considered syntax like this

@Int32
int Truncate64(@Int64 int x);

But unfortunately it does not work with typedefs:

typedef FunctionPointer = @Int32 int TruncationFunction(@Int64 int x);
//                        ^^^^^^ can't put an annotation here

So you end up with two ways of specifying how native types map to Dart types - that is why I opted for the dual-signature approach which works in all cases where you need it. I do agree that the current approach is very verbose and needs to be either improved, shortened or autogenerated.

jaumard commented 5 years ago

I'm agree that the current approach is too verbose and can become really messy / hard to read I really like the annotation approach :) maybe allowing annotation on typedef might be something to consider ? and until it's done just not allowing typedef definition...

dcharkes commented 5 years ago

The problem with not using typedefs is that if you have a function referring to another function you have to repeat the signature of the function that is referred to.

jodinathan commented 5 years ago

+1 to be able to annotate typedefs because working with annotation is always much easier to maintain.

-- Jonathan Rezende

dcharkes commented 5 years ago

@lrhn Would it be possible to have annotations on type defs?

nhachicha commented 5 years ago

@sjindel-google

I'm not sure what you mean by "return an array of primitives (as opposed to a pointer)". What's problematic about returning a pointer to a heap-allocated array and access it via Pointer.load and Pointer.store?

This works, however, it will be inefficient compared to a Dart array/list backed directly by the elements of this primitive array. Otherwise, you'll have to do N FFI call (ptr.load()) to access each element (that's my assumption).

An asynchronous callback will add a task to the microtask queue which will eventually invoke the callback on the mutator thread. An thread inside the appropriate isolate can invoke an asynchronous callback, but will block until the microtask is processed by the mutator thread.

How does this work for Realm?

Internally Realm has the possibility to hand over confined objects between threads, so as long as we resolve the result of the callback in the mutator thread this should work both for synchronous & asynchronous callbacks. Here's the flow I imagine:

Dart (mutator thread)         C/C++ (mutator thread*)           C/C++ (worker_thread)         C/C++ (mutator thread*)                        Dart (mutator thread)
----------------------------- ---------------------------------  ----------------------------- ----------------------------------------------  --------------------------------------------------------
findAllAsync(query, callback) ------> FFI call to native method  -----> spawn a worker pthread ----> hand over the result to the mutator thread ---> trigger callback in Dart with a pointer to results  

* this assumes that the FFI invocation in C/C++ runs within the mutator thread?

sjindel-google commented 5 years ago

@sjindel-google

I'm not sure what you mean by "return an array of primitives (as opposed to a pointer)". What's problematic about returning a pointer to a heap-allocated array and access it via Pointer.load and Pointer.store?

This works, however, it will be inefficient compared to a Dart array/list backed directly by the elements of this primitive array. Otherwise, you'll have to do N FFI call (ptr.load()) to access each element (that's my assumption).

Currently you're correct that ptr.load() is slower than access through e.g. TypedData. However, we are planning to optimize it in the future and it should eventually have similar performance, and I expect we'll create wrapper classes to implement List<int> and Uint8List, etc. out of Pointers.

We are also planning to expose Dart-allocated memory to native code as well. This will always be limited to "leaf" functions: short-running native functions which don't call back into Dart. The problem with this is that GC can be triggered while Dart code is running, and GC can move the Dart-allocated memory. Exposing Dart memory to native code only works then when we pause GC across the native call.

An asynchronous callback will add a task to the microtask queue which will eventually invoke the callback on the mutator thread. An thread inside the appropriate isolate can invoke an asynchronous callback, but will block until the microtask is processed by the mutator thread.

How does this work for Realm?

Internally Realm has the possibility to hand over confined objects between threads, so as long as we resolve the result of the callback in the mutator thread this should work both for synchronous & asynchronous callbacks. Here's the flow I imagine:

Dart (mutator thread)         C/C++ (mutator thread*)           C/C++ (worker_thread)         C/C++ (mutator thread*)                        Dart (mutator thread)
----------------------------- ---------------------------------  ----------------------------- ----------------------------------------------  --------------------------------------------------------
findAllAsync(query, callback) ------> FFI call to native method  -----> spawn a worker pthread ----> hand over the result to the mutator thread ---> trigger callback in Dart with a pointer to results  
  • this assumes that the FFI invocation in C/C++ runs within the mutator thread?

Your assumption is correct -- this will work as you intend. In you diagram, only synchronous calls and callbacks are being used.

dnfield commented 5 years ago

Wouldn't it be less messy to just have some code generation around this rather than trying to annoate typedefs? I haven't thought all the way through it so I may be missing something, but it seems like it should be possible.

ds84182 commented 5 years ago

@dnfield It would be great to have an FFI implementation that is concise and doesn't require code generation (which is a heavy-weight hammer) to interface with C/C++ code. For example, when I was tracking down flutter/flutter#32121 I used FFI outside of a package. It was much quicker than other languages (C++? gotta figure out a build system (CMake? GN? Visual Studio?). Rust? needs to create a project, download the winapi crate, build it...), and I'd like it to stay that way...

To be honest I tend to stay away from code generation in my own projects because of how ergonomics suffer. The analyzer still has issues on Windows when external code updates Dart files, for example, and the edit-refresh cycle for creating your own build steps is sub-par.

lrhn commented 5 years ago

Annotations on typedefs? It's more likely than you think! @eernst Erik, where are we with filling in the annotation holes in the grammar?

mit-mit commented 5 years ago

For the latest update on previewing this feature, please see the updated top-most comment: https://github.com/dart-lang/sdk/issues/34452#issue-359602269

For feedback, questions, and issues regarding the feature, kindly do not post them here in the issue tracker. Please post on the dart-ffi group: https://groups.google.com/forum/#!forum/dart-ffi

truongsinh commented 5 years ago

Wanna follow up whether we can expect iOS support by Dec 2019?

mit-mit commented 5 years ago

Wanna follow up whether we can expect iOS support by Dec 2019?

We hope to have that in the preview fairly soon. Please join the email group mentioned in https://github.com/dart-lang/sdk/issues/34452#issue-359602269 to get updates when that happens.

sjindel-google commented 5 years ago

@Kleak

We've added a new feature to the Pointer api, Pointer.getExternalTypedData. This creates an external TypedData array backed by the C memory from which reads/writes are very efficient.

I modified your benchmark to use this feature, and got the following results:

Before any modifications

The results are similar to what you saw:

LibSodiumSHA256: FooBar LibSodiumSHA256: 0d749abe1377573493e0df74df8d1282e46967754a1ebc7cc6323923a788ad5c LibSodiumSHA256(RunTime): 225.35954929577466 us. LibSodiumSHA512: FooBar LibSodiumSHA512: 0d6fc00052fee1d3ae1880d5dc59b74dd50449ffdea399449223d2c5b792395ce64153b150fc0fc01bfeed30c7347411cfb8a3b17b51fd8aa6c03acfbcd09e7b LibSodiumSHA512(RunTime): 420.7172907025663 us. LibSodiumSHA512FFIOnly: FooBar LibSodiumSHA512FFIOnly: Pointer: address=0x55c12c900c80 LibSodiumSHA512FFIOnly(RunTime): 9.616287064683792 us.

After using getExternalTypedData in CArray.from and CArray.bytes

LibSodiumSHA256: FooBar LibSodiumSHA256: 0d749abe1377573493e0df74df8d1282e46967754a1ebc7cc6323923a788ad5c LibSodiumSHA256(RunTime): 20.373880711047725 us. LibSodiumSHA512: FooBar LibSodiumSHA512: 0d6fc00052fee1d3ae1880d5dc59b74dd50449ffdea399449223d2c5b792395ce64153b150fc0fc01bfeed30c7347411cfb8a3b17b51fd8aa6c03acfbcd09e7b LibSodiumSHA512(RunTime): 22.924955869879188 us. LibSodiumSHA512FFIOnly: FooBar LibSodiumSHA512FFIOnly: Pointer: address=0x55d2a2cd7200 LibSodiumSHA512FFIOnly(RunTime): 9.691031461839255 us

After using getExternalTypedData and caching the allocated arrays

This optimization should not be needed after we optimize calls to Pointer.allocate and Pointer.getExternalTypedData itself.

LibSodiumSHA256: FooBar LibSodiumSHA256: 0d749abe1377573493e0df74df8d1282e46967754a1ebc7cc6323923a788ad5c LibSodiumSHA256(RunTime): 7.746091132671043 us. LibSodiumSHA512: FooBar LibSodiumSHA512: 466f6f426172 LibSodiumSHA512(RunTime): 7.551476501704745 us. LibSodiumSHA512FFIOnly: FooBar LibSodiumSHA512FFIOnly: 0d6fc00052fee1d3ae1880d5dc59b74dd50449ffdea399449223d2c5b792395ce64153b150fc0fc01bfeed30c7347411cfb8a3b17b51fd8aa6c03acfbcd09e7b LibSodiumSHA512FFIOnly(RunTime): 6.1046642593988745 us.

I also switched CArray.bytes to use List.from instead of List.unmodifiable because List.unmodifiable triggers a runtime call.

Result

The final performance (which can be achieved with the FFI today) is more than 2x faster than both PointerCastle and Crypto.

jaumard commented 5 years ago

hey @sjindel-google ! Pretty nice optimization !! Do you mind make a PR of your modifications on the repo ? We would like to integrate those optimizations and as you already have done the migration that would be awesome ^^

pouloghost commented 5 years ago

What's the plan in releasing ffi on flutter? Should I stick to platform channel or should I investigate in exporting native extension myself or just wait for ffi. DESPERATE for calling cpp from dart, these is too much existing code in cpp to rewrite.