├── app ├── .watchmanconfig ├── .ruby-version ├── .bundle │ └── config ├── .gitattributes ├── app.json ├── android │ ├── app │ │ ├── debug.keystore │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── values │ │ │ │ │ │ ├── strings.xml │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ └── drawable │ │ │ │ │ │ └── rn_edit_text_material.xml │ │ │ │ ├── jni │ │ │ │ │ ├── MainApplicationModuleProvider.h │ │ │ │ │ ├── OnLoad.cpp │ │ │ │ │ ├── MainApplicationModuleProvider.cpp │ │ │ │ │ ├── MainComponentsRegistry.h │ │ │ │ │ ├── MainApplicationTurboModuleManagerDelegate.h │ │ │ │ │ ├── MainApplicationTurboModuleManagerDelegate.cpp │ │ │ │ │ ├── Android.mk │ │ │ │ │ └── MainComponentsRegistry.cpp │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── storagebenchmark │ │ │ │ │ ├── newarchitecture │ │ │ │ │ ├── components │ │ │ │ │ │ └── MainComponentsRegistry.java │ │ │ │ │ ├── modules │ │ │ │ │ │ └── MainApplicationTurboModuleManagerDelegate.java │ │ │ │ │ └── MainApplicationReactNativeHost.java │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ └── MainApplication.java │ │ │ └── debug │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── storagebenchmark │ │ │ │ └── ReactNativeFlipper.java │ │ ├── proguard-rules.pro │ │ ├── build_defs.bzl │ │ ├── _BUCK │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ ├── gradle.properties │ ├── build.gradle │ ├── gradlew.bat │ └── gradlew ├── ios │ ├── StorageBenchmark │ │ ├── Images.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── main.m │ │ ├── AppDelegate.h │ │ ├── Info.plist │ │ ├── AppDelegate.mm │ │ └── LaunchScreen.storyboard │ ├── File.swift │ ├── StorageBenchmark.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── StorageBenchmark-Bridging-Header.h │ ├── StorageBenchmarkTests │ │ ├── Info.plist │ │ └── StorageBenchmarkTests.m │ ├── Podfile │ ├── StorageBenchmark.xcodeproj │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── StorageBenchmark.xcscheme │ │ └── project.pbxproj │ └── Podfile.lock ├── .buckconfig ├── .babelrc ├── .prettierrc.js ├── Gemfile ├── index.js ├── src │ ├── storages │ │ ├── MMKV.ts │ │ ├── AsyncStorage.ts │ │ ├── ExpoSecureStorage.ts │ │ ├── MMKVEncrypted.ts │ │ ├── ReactNativeKeychain.ts │ │ ├── Realm.ts │ │ ├── SQLite.ts │ │ └── WatermelonDB.ts │ └── App.tsx ├── __tests__ │ └── App-test.tsx ├── metro.config.js ├── .eslintrc.js ├── .gitignore ├── package.json ├── Gemfile.lock └── tsconfig.json ├── img ├── graph.png └── comparison.png └── README.md /app/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.4 2 | -------------------------------------------------------------------------------- /app/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /img/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/img/graph.png -------------------------------------------------------------------------------- /app/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "StorageBenchmark", 3 | "displayName": "StorageBenchmark" 4 | } -------------------------------------------------------------------------------- /img/comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/img/comparison.png -------------------------------------------------------------------------------- /app/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/app/android/app/debug.keystore -------------------------------------------------------------------------------- /app/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | StorageBenchmark 3 | 4 | -------------------------------------------------------------------------------- /app/ios/StorageBenchmark/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /app/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /app/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/app/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/ios/File.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // StorageBenchmark 4 | // 5 | // Created by Marc Rousavy on 10.05.22. 6 | // 7 | 8 | import Foundation 9 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["module:metro-react-native-babel-preset"], 3 | "plugins": [ 4 | ["@babel/plugin-proposal-decorators", { "legacy": true }] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: true, 4 | bracketSpacing: false, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/StorageBenchmark/HEAD/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby '2.7.4' 5 | 6 | gem 'cocoapods', '~> 1.11', '>= 1.11.2' 7 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry} from 'react-native'; 6 | import App from './src/App'; 7 | import {name as appName} from './app.json'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /app/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /app/ios/StorageBenchmark/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/storages/MMKV.ts: -------------------------------------------------------------------------------- 1 | import {MMKV} from 'react-native-mmkv'; 2 | 3 | const storage = new MMKV(); 4 | 5 | storage.clearAll(); 6 | 7 | const key = 'k'; 8 | storage.set(key, 'hello'); 9 | 10 | export function getFromMMKV(): string | undefined { 11 | return storage.getString(key); 12 | } 13 | -------------------------------------------------------------------------------- /app/ios/StorageBenchmark/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | @interface AppDelegate : EXAppDelegateWrapper 6 | 7 | @property (nonatomic, strong) UIWindow *window; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /app/src/storages/AsyncStorage.ts: -------------------------------------------------------------------------------- 1 | import AsyncStorage from '@react-native-async-storage/async-storage'; 2 | 3 | const key = 'k'; 4 | 5 | AsyncStorage.clear(); 6 | AsyncStorage.setItem(key, 'hello'); 7 | 8 | export async function getFromAsyncStorage(): Promise { 9 | return AsyncStorage.getItem(key); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/storages/ExpoSecureStorage.ts: -------------------------------------------------------------------------------- 1 | import {getItemAsync, setItemAsync, deleteItemAsync} from 'expo-secure-store'; 2 | 3 | const key = 'k'; 4 | 5 | deleteItemAsync(key) 6 | setItemAsync(key, "hello") 7 | 8 | export async function getFromExpoSecureStorage(): Promise { 9 | return getItemAsync(key) 10 | } 11 | -------------------------------------------------------------------------------- /app/ios/StorageBenchmark.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/ios/StorageBenchmark.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/__tests__/App-test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import 'react-native'; 6 | import React from 'react'; 7 | import App from '../src/App'; 8 | 9 | // Note: test renderer must be required after react-native. 10 | import renderer from 'react-test-renderer'; 11 | 12 | it('renders correctly', () => { 13 | renderer.create(); 14 | }); 15 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: true, 14 | }, 15 | }), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /app/src/storages/MMKVEncrypted.ts: -------------------------------------------------------------------------------- 1 | import {MMKV} from 'react-native-mmkv'; 2 | 3 | const storage = new MMKV({ 4 | id: 'encrypted-mmkv-storage', 5 | encryptionKey: 'hunter2', 6 | }); 7 | 8 | storage.clearAll(); 9 | 10 | const key = 'k'; 11 | storage.set(key, 'hello'); 12 | 13 | export function getFromMMKVEncrypted(): string | undefined { 14 | return storage.getString(key); 15 | } 16 | -------------------------------------------------------------------------------- /app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native-community', 4 | parser: '@typescript-eslint/parser', 5 | plugins: ['@typescript-eslint'], 6 | overrides: [ 7 | { 8 | files: ['*.ts', '*.tsx'], 9 | rules: { 10 | '@typescript-eslint/no-shadow': ['error'], 11 | 'no-shadow': 'off', 12 | 'no-undef': 'off', 13 | }, 14 | }, 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /app/android/app/src/main/jni/MainApplicationModuleProvider.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace facebook { 9 | namespace react { 10 | 11 | std::shared_ptr MainApplicationModuleProvider( 12 | const std::string moduleName, 13 | const JavaTurboModule::InitParams ¶ms); 14 | 15 | } // namespace react 16 | } // namespace facebook 17 | -------------------------------------------------------------------------------- /app/src/storages/ReactNativeKeychain.ts: -------------------------------------------------------------------------------- 1 | import {resetGenericPassword, setGenericPassword, getGenericPassword} from 'react-native-keychain'; 2 | 3 | const key = 'k'; 4 | 5 | resetGenericPassword() 6 | setGenericPassword(key, "hello") 7 | 8 | export async function getFromReactNativeKeychain(): Promise { 9 | const result = await getGenericPassword() 10 | if (result) return result.password; 11 | return undefined; 12 | } 13 | -------------------------------------------------------------------------------- /app/android/app/src/main/jni/OnLoad.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "MainApplicationTurboModuleManagerDelegate.h" 3 | #include "MainComponentsRegistry.h" 4 | 5 | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { 6 | return facebook::jni::initialize(vm, [] { 7 | facebook::react::MainApplicationTurboModuleManagerDelegate:: 8 | registerNatives(); 9 | facebook::react::MainComponentsRegistry::registerNatives(); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /app/ios/StorageBenchmark-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import 6 | #import 7 | #import 8 | #import 9 | #import 10 | 11 | // Silence warning 12 | #import "../node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/SupportingFiles/Bridging.h" 13 | -------------------------------------------------------------------------------- /app/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /app/src/storages/Realm.ts: -------------------------------------------------------------------------------- 1 | import Realm from 'realm'; 2 | 3 | const TestSchema = { 4 | name: 'Test', 5 | properties: { 6 | _id: 'int', 7 | value: 'string', 8 | }, 9 | primaryKey: '_id', 10 | }; 11 | 12 | const realm = new Realm({ 13 | schema: [TestSchema], 14 | }); 15 | 16 | realm.write(() => { 17 | realm.deleteAll(); 18 | realm.create('Test', { 19 | _id: 1, 20 | value: 'hello', 21 | }); 22 | }); 23 | 24 | export function getFromRealm() { 25 | const object = realm.objectForPrimaryKey('Test', 1); 26 | return object.value; 27 | } 28 | -------------------------------------------------------------------------------- /app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'StorageBenchmark' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | includeBuild('../node_modules/react-native-gradle-plugin') 5 | 6 | if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") { 7 | include(":ReactAndroid") 8 | project(":ReactAndroid").projectDir = file('../node_modules/react-native/ReactAndroid') 9 | } 10 | 11 | apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle") 12 | useExpoModules() -------------------------------------------------------------------------------- /app/android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /app/ios/StorageBenchmarkTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/android/app/src/main/jni/MainApplicationModuleProvider.cpp: -------------------------------------------------------------------------------- 1 | #include "MainApplicationModuleProvider.h" 2 | 3 | #include 4 | 5 | namespace facebook { 6 | namespace react { 7 | 8 | std::shared_ptr MainApplicationModuleProvider( 9 | const std::string moduleName, 10 | const JavaTurboModule::InitParams ¶ms) { 11 | // Here you can provide your own module provider for TurboModules coming from 12 | // either your application or from external libraries. The approach to follow 13 | // is similar to the following (for a library called `samplelibrary`: 14 | // 15 | // auto module = samplelibrary_ModuleProvider(moduleName, params); 16 | // if (module != nullptr) { 17 | // return module; 18 | // } 19 | // return rncore_ModuleProvider(moduleName, params); 20 | return rncore_ModuleProvider(moduleName, params); 21 | } 22 | 23 | } // namespace react 24 | } // namespace facebook 25 | -------------------------------------------------------------------------------- /app/android/app/src/main/jni/MainComponentsRegistry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace facebook { 9 | namespace react { 10 | 11 | class MainComponentsRegistry 12 | : public facebook::jni::HybridClass { 13 | public: 14 | // Adapt it to the package you used for your Java class. 15 | constexpr static auto kJavaDescriptor = 16 | "Lcom/storagebenchmark/newarchitecture/components/MainComponentsRegistry;"; 17 | 18 | static void registerNatives(); 19 | 20 | MainComponentsRegistry(ComponentFactory *delegate); 21 | 22 | private: 23 | static std::shared_ptr 24 | sharedProviderRegistry(); 25 | 26 | static jni::local_ref initHybrid( 27 | jni::alias_ref, 28 | ComponentFactory *delegate); 29 | }; 30 | 31 | } // namespace react 32 | } // namespace facebook 33 | -------------------------------------------------------------------------------- /app/src/storages/SQLite.ts: -------------------------------------------------------------------------------- 1 | import {QuickSQLite} from 'react-native-quick-sqlite'; 2 | 3 | const db = 'myDatabase'; 4 | 5 | const dbOpenResult = QuickSQLite.open(db, 'databases'); 6 | 7 | // status === 1, operation failed 8 | if (dbOpenResult.status) { 9 | console.error('SQLite Database could not be opened'); 10 | } 11 | 12 | let result = QuickSQLite.executeSql(db, 'DROP TABLE IF EXISTS Benchmark', []); 13 | if (result.status) { 14 | console.error('SQLite: Failed to create table!', result); 15 | } 16 | 17 | QuickSQLite.executeSql( 18 | db, 19 | 'CREATE TABLE IF NOT EXISTS Benchmark(value VARCHAR(30))', 20 | [], 21 | ); 22 | QuickSQLite.executeSql(db, 'INSERT INTO Benchmark (value) VALUES (:value)', [ 23 | 'hello', 24 | ]); 25 | 26 | export function getFromSQLite(): string | undefined { 27 | let {status, rows} = QuickSQLite.executeSql( 28 | db, 29 | 'SELECT * FROM `Benchmark`', 30 | [], 31 | ); 32 | if (rows == null || rows.length < 1) { 33 | throw new Error(`Failed to get Values! ${JSON.stringify(status)}`); 34 | } 35 | 36 | const row = rows.item(0); 37 | return row.value; 38 | } 39 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | *.hprof 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | !debug.keystore 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://docs.fastlane.tools/best-practices/source-control/ 51 | 52 | */fastlane/report.xml 53 | */fastlane/Preview.html 54 | */fastlane/screenshots 55 | 56 | # Bundle artifact 57 | *.jsbundle 58 | 59 | # Ruby / CocoaPods 60 | /ios/Pods/ 61 | /vendor/bundle/ 62 | -------------------------------------------------------------------------------- /app/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/ios/StorageBenchmark/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | namespace facebook { 8 | namespace react { 9 | 10 | class MainApplicationTurboModuleManagerDelegate 11 | : public jni::HybridClass< 12 | MainApplicationTurboModuleManagerDelegate, 13 | TurboModuleManagerDelegate> { 14 | public: 15 | // Adapt it to the package you used for your Java class. 16 | static constexpr auto kJavaDescriptor = 17 | "Lcom/storagebenchmark/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;"; 18 | 19 | static jni::local_ref initHybrid(jni::alias_ref); 20 | 21 | static void registerNatives(); 22 | 23 | std::shared_ptr getTurboModule( 24 | const std::string name, 25 | const std::shared_ptr jsInvoker) override; 26 | std::shared_ptr getTurboModule( 27 | const std::string name, 28 | const JavaTurboModule::InitParams ¶ms) override; 29 | 30 | /** 31 | * Test-only method. Allows user to verify whether a TurboModule can be 32 | * created by instances of this class. 33 | */ 34 | bool canCreateTurboModule(std::string name); 35 | }; 36 | 37 | } // namespace react 38 | } // namespace facebook 39 | -------------------------------------------------------------------------------- /app/android/app/src/main/java/com/storagebenchmark/newarchitecture/components/MainComponentsRegistry.java: -------------------------------------------------------------------------------- 1 | package com.storagebenchmark.newarchitecture.components; 2 | 3 | import com.facebook.jni.HybridData; 4 | import com.facebook.proguard.annotations.DoNotStrip; 5 | import com.facebook.react.fabric.ComponentFactory; 6 | import com.facebook.soloader.SoLoader; 7 | 8 | /** 9 | * Class responsible to load the custom Fabric Components. This class has native methods and needs a 10 | * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ 11 | * folder for you). 12 | * 13 | *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the 14 | * `newArchEnabled` property). Is ignored otherwise. 15 | */ 16 | @DoNotStrip 17 | public class MainComponentsRegistry { 18 | static { 19 | SoLoader.loadLibrary("fabricjni"); 20 | } 21 | 22 | @DoNotStrip private final HybridData mHybridData; 23 | 24 | @DoNotStrip 25 | private native HybridData initHybrid(ComponentFactory componentFactory); 26 | 27 | @DoNotStrip 28 | private MainComponentsRegistry(ComponentFactory componentFactory) { 29 | mHybridData = initHybrid(componentFactory); 30 | } 31 | 32 | @DoNotStrip 33 | public static MainComponentsRegistry register(ComponentFactory componentFactory) { 34 | return new MainComponentsRegistry(componentFactory); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/storages/WatermelonDB.ts: -------------------------------------------------------------------------------- 1 | import {appSchema, Database, Model, tableSchema} from '@nozbe/watermelondb'; 2 | import {field} from '@nozbe/watermelondb/decorators'; 3 | import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'; 4 | 5 | const TABLE = 'Test'; 6 | 7 | class TestModel extends Model { 8 | static table = TABLE; 9 | 10 | @field('value') value; 11 | } 12 | 13 | const schema = appSchema({ 14 | version: 1, 15 | tables: [ 16 | tableSchema({ 17 | name: TABLE, 18 | columns: [{name: 'value', type: 'string'}], 19 | }), 20 | ], 21 | }); 22 | 23 | // First, create the adapter to the underlying database: 24 | const adapter = new SQLiteAdapter({ 25 | schema: schema, 26 | }); 27 | 28 | const database = new Database({ 29 | adapter: adapter, 30 | modelClasses: [TestModel], 31 | }); 32 | 33 | const table = database.collections.get(TABLE); 34 | 35 | const promise = database.write(async () => { 36 | await database.unsafeResetDatabase(); 37 | try { 38 | const entry = await table.create(m => { 39 | m._raw.id = 'hello'; 40 | m.value = 'hello'; 41 | }); 42 | return entry; 43 | } catch (e) { 44 | console.error('WatermelonDB: Failed to set value!', e); 45 | } 46 | }); 47 | let isCreated = false; 48 | 49 | export async function getFromWatermelonDB(): Promise { 50 | if (!isCreated) { 51 | await promise; 52 | isCreated = true; 53 | } 54 | const row = await table.find('hello'); 55 | return row.value; 56 | } 57 | -------------------------------------------------------------------------------- /app/android/app/_BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.storagebenchmark", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.storagebenchmark", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /app/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp: -------------------------------------------------------------------------------- 1 | #include "MainApplicationTurboModuleManagerDelegate.h" 2 | #include "MainApplicationModuleProvider.h" 3 | 4 | namespace facebook { 5 | namespace react { 6 | 7 | jni::local_ref 8 | MainApplicationTurboModuleManagerDelegate::initHybrid( 9 | jni::alias_ref) { 10 | return makeCxxInstance(); 11 | } 12 | 13 | void MainApplicationTurboModuleManagerDelegate::registerNatives() { 14 | registerHybrid({ 15 | makeNativeMethod( 16 | "initHybrid", MainApplicationTurboModuleManagerDelegate::initHybrid), 17 | makeNativeMethod( 18 | "canCreateTurboModule", 19 | MainApplicationTurboModuleManagerDelegate::canCreateTurboModule), 20 | }); 21 | } 22 | 23 | std::shared_ptr 24 | MainApplicationTurboModuleManagerDelegate::getTurboModule( 25 | const std::string name, 26 | const std::shared_ptr jsInvoker) { 27 | // Not implemented yet: provide pure-C++ NativeModules here. 28 | return nullptr; 29 | } 30 | 31 | std::shared_ptr 32 | MainApplicationTurboModuleManagerDelegate::getTurboModule( 33 | const std::string name, 34 | const JavaTurboModule::InitParams ¶ms) { 35 | return MainApplicationModuleProvider(name, params); 36 | } 37 | 38 | bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule( 39 | std::string name) { 40 | return getTurboModule(name, nullptr) != nullptr || 41 | getTurboModule(name, {.moduleName = name}) != nullptr; 42 | } 43 | 44 | } // namespace react 45 | } // namespace facebook 46 | -------------------------------------------------------------------------------- /app/android/app/src/main/java/com/storagebenchmark/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.storagebenchmark; 2 | import expo.modules.ReactActivityDelegateWrapper; 3 | 4 | import com.facebook.react.ReactActivity; 5 | import com.facebook.react.ReactActivityDelegate; 6 | import com.facebook.react.ReactRootView; 7 | 8 | public class MainActivity extends ReactActivity { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. This is used to schedule 12 | * rendering of the component. 13 | */ 14 | @Override 15 | protected String getMainComponentName() { 16 | return "StorageBenchmark"; 17 | } 18 | 19 | /** 20 | * Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and 21 | * you can specify the rendered you wish to use (Fabric or the older renderer). 22 | */ 23 | @Override 24 | protected ReactActivityDelegate createReactActivityDelegate() { 25 | return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, new MainActivityDelegate(this, getMainComponentName())); 26 | } 27 | 28 | public static class MainActivityDelegate extends ReactActivityDelegate { 29 | public MainActivityDelegate(ReactActivity activity, String mainComponentName) { 30 | super(activity, mainComponentName); 31 | } 32 | 33 | @Override 34 | protected ReactRootView createRootView() { 35 | ReactRootView reactRootView = new ReactRootView(getContext()); 36 | // If you opted-in for the New Architecture, we enable the Fabric Renderer. 37 | reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); 38 | return reactRootView; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storagebenchmark", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "pods": "cd ios && pod install", 10 | "test": "jest", 11 | "lint": "eslint . --ext .js,.jsx,.ts,.tsx" 12 | }, 13 | "dependencies": { 14 | "@nozbe/watermelondb": "^0.24.0", 15 | "@react-native-async-storage/async-storage": "^1.17.10", 16 | "expo": "^45.0.0", 17 | "expo-secure-store": "~11.2.0", 18 | "react": "17.0.2", 19 | "react-native": "0.68.7", 20 | "react-native-keychain": "^8.1.2", 21 | "react-native-mmkv": "^2.4.3", 22 | "react-native-quick-sqlite": "^4.0.7", 23 | "realm": "11.0.0-rc.0" 24 | }, 25 | "devDependencies": { 26 | "@babel/core": "^7.12.9", 27 | "@babel/plugin-proposal-decorators": "^7.17.9", 28 | "@babel/runtime": "^7.12.5", 29 | "@react-native-community/eslint-config": "^2.0.0", 30 | "@types/jest": "^26.0.23", 31 | "@types/react-native": "^0.67.3", 32 | "@types/react-test-renderer": "^17.0.1", 33 | "@typescript-eslint/eslint-plugin": "^5.17.0", 34 | "@typescript-eslint/parser": "^5.17.0", 35 | "babel-jest": "^26.6.3", 36 | "eslint": "^7.32.0", 37 | "jest": "^26.6.3", 38 | "metro-react-native-babel-preset": "^0.67.0", 39 | "react-test-renderer": "17.0.2", 40 | "typescript": "^4.4.4" 41 | }, 42 | "resolutions": { 43 | "@types/react": "^17" 44 | }, 45 | "jest": { 46 | "preset": "react-native", 47 | "moduleFileExtensions": [ 48 | "ts", 49 | "tsx", 50 | "js", 51 | "jsx", 52 | "json", 53 | "node" 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/android/app/src/main/jni/Android.mk: -------------------------------------------------------------------------------- 1 | THIS_DIR := $(call my-dir) 2 | 3 | include $(REACT_ANDROID_DIR)/Android-prebuilt.mk 4 | 5 | # If you wish to add a custom TurboModule or Fabric component in your app you 6 | # will have to include the following autogenerated makefile. 7 | # include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk 8 | include $(CLEAR_VARS) 9 | 10 | LOCAL_PATH := $(THIS_DIR) 11 | 12 | # You can customize the name of your application .so file here. 13 | LOCAL_MODULE := storagebenchmark_appmodules 14 | 15 | LOCAL_C_INCLUDES := $(LOCAL_PATH) 16 | LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) 17 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) 18 | 19 | # If you wish to add a custom TurboModule or Fabric component in your app you 20 | # will have to uncomment those lines to include the generated source 21 | # files from the codegen (placed in $(GENERATED_SRC_DIR)/codegen/jni) 22 | # 23 | # LOCAL_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni 24 | # LOCAL_SRC_FILES += $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp) 25 | # LOCAL_EXPORT_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni 26 | 27 | # Here you should add any native library you wish to depend on. 28 | LOCAL_SHARED_LIBRARIES := \ 29 | libfabricjni \ 30 | libfbjni \ 31 | libfolly_futures \ 32 | libfolly_json \ 33 | libglog \ 34 | libjsi \ 35 | libreact_codegen_rncore \ 36 | libreact_debug \ 37 | libreact_nativemodule_core \ 38 | libreact_render_componentregistry \ 39 | libreact_render_core \ 40 | libreact_render_debug \ 41 | libreact_render_graphics \ 42 | librrc_view \ 43 | libruntimeexecutor \ 44 | libturbomodulejsijni \ 45 | libyoga 46 | 47 | LOCAL_CFLAGS := -DLOG_TAG=\"ReactNative\" -fexceptions -frtti -std=c++17 -Wall 48 | 49 | include $(BUILD_SHARED_LIBRARY) 50 | -------------------------------------------------------------------------------- /app/ios/StorageBenchmark/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | StorageBenchmark 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSExceptionDomains 30 | 31 | localhost 32 | 33 | NSExceptionAllowsInsecureHTTPLoads 34 | 35 | 36 | 37 | 38 | NSLocationWhenInUseUsageDescription 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/ios/Podfile: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") 2 | require_relative '../node_modules/react-native/scripts/react_native_pods' 3 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 4 | 5 | platform :ios, '12.0' 6 | install! 'cocoapods', :deterministic_uuids => false 7 | 8 | target 'StorageBenchmark' do 9 | use_expo_modules! 10 | post_integrate do |installer| 11 | begin 12 | expo_patch_react_imports!(installer) 13 | rescue => e 14 | Pod::UI.warn e 15 | end 16 | end 17 | config = use_native_modules! 18 | 19 | # Flags change depending on the env values. 20 | flags = get_default_flags() 21 | 22 | use_react_native!( 23 | :path => config[:reactNativePath], 24 | # to enable hermes on iOS, change `false` to `true` and then install pods 25 | :hermes_enabled => true, 26 | :fabric_enabled => flags[:fabric_enabled], 27 | # An absolute path to your application root. 28 | :app_path => "#{Pod::Config.instance.installation_root}/.." 29 | ) 30 | 31 | # If you're using autolinking, this line might not be needed 32 | 33 | # NOTE: Do not remove, needed to keep WatermelonDB compiling: 34 | pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi', :modular_headers => true 35 | 36 | # NOTE: This is required as of v0.23 37 | pod 'simdjson', path: '../node_modules/@nozbe/simdjson' 38 | 39 | target 'StorageBenchmarkTests' do 40 | inherit! :complete 41 | # Pods for testing 42 | end 43 | 44 | # Enables Flipper. 45 | # 46 | # Note that if you have use_frameworks! enabled, Flipper will not work and 47 | # you should disable the next line. 48 | use_flipper!() 49 | 50 | post_install do |installer| 51 | react_native_post_install(installer) 52 | __apply_Xcode_12_5_M1_post_install_workaround(installer) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.125.0 29 | 30 | # Use this property to specify which architecture you want to build. 31 | # You can also override it from the CLI using 32 | # ./gradlew -PreactNativeArchitectures=x86_64 33 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 34 | 35 | # Use this property to enable support to the new architecture. 36 | # This will allow you to use TurboModules and the Fabric render in 37 | # your application. You should enable this flag either if you want 38 | # to write custom TurboModules/Fabric components OR use libraries that 39 | # are providing them. 40 | newArchEnabled=false 41 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/android/app/src/main/java/com/storagebenchmark/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java: -------------------------------------------------------------------------------- 1 | package com.storagebenchmark.newarchitecture.modules; 2 | 3 | import com.facebook.jni.HybridData; 4 | import com.facebook.react.ReactPackage; 5 | import com.facebook.react.ReactPackageTurboModuleManagerDelegate; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.soloader.SoLoader; 8 | import java.util.List; 9 | 10 | /** 11 | * Class responsible to load the TurboModules. This class has native methods and needs a 12 | * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ 13 | * folder for you). 14 | * 15 | *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the 16 | * `newArchEnabled` property). Is ignored otherwise. 17 | */ 18 | public class MainApplicationTurboModuleManagerDelegate 19 | extends ReactPackageTurboModuleManagerDelegate { 20 | 21 | private static volatile boolean sIsSoLibraryLoaded; 22 | 23 | protected MainApplicationTurboModuleManagerDelegate( 24 | ReactApplicationContext reactApplicationContext, List packages) { 25 | super(reactApplicationContext, packages); 26 | } 27 | 28 | protected native HybridData initHybrid(); 29 | 30 | native boolean canCreateTurboModule(String moduleName); 31 | 32 | public static class Builder extends ReactPackageTurboModuleManagerDelegate.Builder { 33 | protected MainApplicationTurboModuleManagerDelegate build( 34 | ReactApplicationContext context, List packages) { 35 | return new MainApplicationTurboModuleManagerDelegate(context, packages); 36 | } 37 | } 38 | 39 | @Override 40 | protected synchronized void maybeLoadOtherSoLibraries() { 41 | if (!sIsSoLibraryLoaded) { 42 | // If you change the name of your application .so file in the Android.mk file, 43 | // make sure you update the name here as well. 44 | SoLoader.loadLibrary("storagebenchmark_appmodules"); 45 | sIsSoLibraryLoaded = true; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Storage Benchmarks 2 | 3 | This is a benchmark app to compare popular storage solutions for React Native. 4 | 5 | It's running React Native 0.68, with Hermes enabled. 6 | 7 | The Benchmark consists of calling a _get_ operation (retrieve one value from the database) a thousand times. 8 | 9 | Here are the results, ranked from fastest to slowest: 10 | 11 | 1. [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv): **12ms** 👑 12 | 2. [WatermelonDB](https://github.com/Nozbe/WatermelonDB): **53ms** 13 | 3. [RealmDB](https://github.com/realm/realm-js): **81ms** 14 | 4. [react-native-quick-sqlite](https://github.com/ospfranco/react-native-quick-sqlite): **82ms** 15 | 5. [AsyncStorage](https://github.com/react-native-async-storage/async-storage): **242ms** 16 | 17 | MMKV is **20x** faster than AsyncStorage (slowest), and **4x** faster than WatermelonDB (second fastest)! 18 | 19 |

20 | 21 |
22 | 23 | Output in the console: 24 | 25 |
26 | 27 |
28 | 29 | > Tested on an iPhone 11 Pro, Hermes, Debug 30 | 31 | ## Run it 32 | 33 | 1. Clone the repo and navigate to the `app/` directory 34 | 2. Run `yarn` 35 | 3. Run `yarn pods` 36 | 4. Run `yarn ios --device "YOURPHONENAME"` 37 | 38 | You can also omit the `--device "YOURPHONENAME"` flag, but running on a Simulator always gives different results than on an actual device. 39 | 40 | ### JS Engine 41 | 42 | The benchmark project currently uses Hermes. To benchmark using JSC instead, set `enable_hermes` to `false` in the [`Podfile`](./app/ios/Podfile). 43 | 44 | ### Hardware 45 | 46 | The above results were tested on an iPhone 11 Pro. Results may differ on different iPhones or Android Phones. 47 | 48 | ### Debug 49 | 50 | The above results were tested in a debug build. Release mode builds come with many optimizations and are therefore faster than debug. 51 | 52 | ### Operations 53 | 54 | The above results were tested using _get_ operations for a single string key (value: `'hello'`). Results may differ when using other operations, such as _set_, _delete_, _update_, and more. 55 | -------------------------------------------------------------------------------- /app/android/build.gradle: -------------------------------------------------------------------------------- 1 | import org.apache.tools.ant.taskdefs.condition.Os 2 | 3 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 4 | 5 | buildscript { 6 | ext { 7 | buildToolsVersion = "31.0.0" 8 | minSdkVersion = 21 9 | compileSdkVersion = 31 10 | targetSdkVersion = 31 11 | kotlinVersion = '1.3.50' 12 | 13 | if (System.properties['os.arch'] == "aarch64") { 14 | // For M1 Users we need to use the NDK 24 which added support for aarch64 15 | ndkVersion = "24.0.8215888" 16 | } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { 17 | // For Android Users, we need to use NDK 23, otherwise the build will 18 | // fail due to paths longer than the OS limit 19 | ndkVersion = "23.1.7779620" 20 | } else { 21 | // Otherwise we default to the side-by-side NDK version from AGP. 22 | ndkVersion = "21.4.7075529" 23 | } 24 | } 25 | repositories { 26 | google() 27 | mavenCentral() 28 | } 29 | dependencies { 30 | classpath("com.android.tools.build:gradle:7.0.4") 31 | classpath("com.facebook.react:react-native-gradle-plugin") 32 | classpath("de.undercouch:gradle-download-task:4.1.2") 33 | // NOTE: Do not place your application dependencies here; they belong 34 | // in the individual module build.gradle files 35 | } 36 | } 37 | 38 | allprojects { 39 | repositories { 40 | maven { 41 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 42 | url("$rootDir/../node_modules/react-native/android") 43 | } 44 | maven { 45 | // Android JSC is installed from npm 46 | url("$rootDir/../node_modules/jsc-android/dist") 47 | } 48 | mavenCentral { 49 | // We don't want to fetch react-native from Maven Central as there are 50 | // older versions over there. 51 | content { 52 | excludeGroup "com.facebook.react" 53 | } 54 | } 55 | google() 56 | maven { url 'https://www.jitpack.io' } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/ios/StorageBenchmarkTests/StorageBenchmarkTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface StorageBenchmarkTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation StorageBenchmarkTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction( 38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 39 | if (level >= RCTLogLevelError) { 40 | redboxError = message; 41 | } 42 | }); 43 | #endif 44 | 45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 48 | 49 | foundElement = [self findSubviewInView:vc.view 50 | matching:^BOOL(UIView *view) { 51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 52 | return YES; 53 | } 54 | return NO; 55 | }]; 56 | } 57 | 58 | #ifdef DEBUG 59 | RCTSetLogFunction(RCTDefaultLogFunction); 60 | #endif 61 | 62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /app/android/app/src/main/jni/MainComponentsRegistry.cpp: -------------------------------------------------------------------------------- 1 | #include "MainComponentsRegistry.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace facebook { 9 | namespace react { 10 | 11 | MainComponentsRegistry::MainComponentsRegistry(ComponentFactory *delegate) {} 12 | 13 | std::shared_ptr 14 | MainComponentsRegistry::sharedProviderRegistry() { 15 | auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry(); 16 | 17 | // Custom Fabric Components go here. You can register custom 18 | // components coming from your App or from 3rd party libraries here. 19 | // 20 | // providerRegistry->add(concreteComponentDescriptorProvider< 21 | // AocViewerComponentDescriptor>()); 22 | return providerRegistry; 23 | } 24 | 25 | jni::local_ref 26 | MainComponentsRegistry::initHybrid( 27 | jni::alias_ref, 28 | ComponentFactory *delegate) { 29 | auto instance = makeCxxInstance(delegate); 30 | 31 | auto buildRegistryFunction = 32 | [](EventDispatcher::Weak const &eventDispatcher, 33 | ContextContainer::Shared const &contextContainer) 34 | -> ComponentDescriptorRegistry::Shared { 35 | auto registry = MainComponentsRegistry::sharedProviderRegistry() 36 | ->createComponentDescriptorRegistry( 37 | {eventDispatcher, contextContainer}); 38 | 39 | auto mutableRegistry = 40 | std::const_pointer_cast(registry); 41 | 42 | mutableRegistry->setFallbackComponentDescriptor( 43 | std::make_shared( 44 | ComponentDescriptorParameters{ 45 | eventDispatcher, contextContainer, nullptr})); 46 | 47 | return registry; 48 | }; 49 | 50 | delegate->buildRegistryFunction = buildRegistryFunction; 51 | return instance; 52 | } 53 | 54 | void MainComponentsRegistry::registerNatives() { 55 | registerHybrid({ 56 | makeNativeMethod("initHybrid", MainComponentsRegistry::initHybrid), 57 | }); 58 | } 59 | 60 | } // namespace react 61 | } // namespace facebook 62 | -------------------------------------------------------------------------------- /app/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.5) 5 | rexml 6 | activesupport (6.1.5) 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | i18n (>= 1.6, < 2) 9 | minitest (>= 5.1) 10 | tzinfo (~> 2.0) 11 | zeitwerk (~> 2.3) 12 | addressable (2.8.0) 13 | public_suffix (>= 2.0.2, < 5.0) 14 | algoliasearch (1.27.5) 15 | httpclient (~> 2.8, >= 2.8.3) 16 | json (>= 1.5.1) 17 | atomos (0.1.3) 18 | claide (1.1.0) 19 | cocoapods (1.11.3) 20 | addressable (~> 2.8) 21 | claide (>= 1.0.2, < 2.0) 22 | cocoapods-core (= 1.11.3) 23 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 24 | cocoapods-downloader (>= 1.4.0, < 2.0) 25 | cocoapods-plugins (>= 1.0.0, < 2.0) 26 | cocoapods-search (>= 1.0.0, < 2.0) 27 | cocoapods-trunk (>= 1.4.0, < 2.0) 28 | cocoapods-try (>= 1.1.0, < 2.0) 29 | colored2 (~> 3.1) 30 | escape (~> 0.0.4) 31 | fourflusher (>= 2.3.0, < 3.0) 32 | gh_inspector (~> 1.0) 33 | molinillo (~> 0.8.0) 34 | nap (~> 1.0) 35 | ruby-macho (>= 1.0, < 3.0) 36 | xcodeproj (>= 1.21.0, < 2.0) 37 | cocoapods-core (1.11.3) 38 | activesupport (>= 5.0, < 7) 39 | addressable (~> 2.8) 40 | algoliasearch (~> 1.0) 41 | concurrent-ruby (~> 1.1) 42 | fuzzy_match (~> 2.0.4) 43 | nap (~> 1.0) 44 | netrc (~> 0.11) 45 | public_suffix (~> 4.0) 46 | typhoeus (~> 1.0) 47 | cocoapods-deintegrate (1.0.5) 48 | cocoapods-downloader (1.6.3) 49 | cocoapods-plugins (1.0.0) 50 | nap 51 | cocoapods-search (1.0.1) 52 | cocoapods-trunk (1.6.0) 53 | nap (>= 0.8, < 2.0) 54 | netrc (~> 0.11) 55 | cocoapods-try (1.2.0) 56 | colored2 (3.1.2) 57 | concurrent-ruby (1.1.10) 58 | escape (0.0.4) 59 | ethon (0.15.0) 60 | ffi (>= 1.15.0) 61 | ffi (1.15.5) 62 | fourflusher (2.3.1) 63 | fuzzy_match (2.0.4) 64 | gh_inspector (1.1.3) 65 | httpclient (2.8.3) 66 | i18n (1.10.0) 67 | concurrent-ruby (~> 1.0) 68 | json (2.6.1) 69 | minitest (5.15.0) 70 | molinillo (0.8.0) 71 | nanaimo (0.3.0) 72 | nap (1.1.0) 73 | netrc (0.11.0) 74 | public_suffix (4.0.7) 75 | rexml (3.2.5) 76 | ruby-macho (2.5.1) 77 | typhoeus (1.4.0) 78 | ethon (>= 0.9.0) 79 | tzinfo (2.0.4) 80 | concurrent-ruby (~> 1.0) 81 | xcodeproj (1.21.0) 82 | CFPropertyList (>= 2.3.3, < 4.0) 83 | atomos (~> 0.1.3) 84 | claide (>= 1.0.2, < 2.0) 85 | colored2 (~> 3.1) 86 | nanaimo (~> 0.3.0) 87 | rexml (~> 3.2.4) 88 | zeitwerk (2.5.4) 89 | 90 | PLATFORMS 91 | ruby 92 | 93 | DEPENDENCIES 94 | cocoapods (~> 1.11, >= 1.11.2) 95 | 96 | RUBY VERSION 97 | ruby 2.7.4p191 98 | 99 | BUNDLED WITH 100 | 2.2.27 101 | -------------------------------------------------------------------------------- /app/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | import { 3 | Button, 4 | SafeAreaView, 5 | StatusBar, 6 | StyleSheet, 7 | useColorScheme, 8 | } from 'react-native'; 9 | import {getFromAsyncStorage} from './storages/AsyncStorage'; 10 | import {getFromMMKV} from './storages/MMKV'; 11 | import {getFromReactNativeKeychain} from './storages/ReactNativeKeychain'; 12 | import {getFromRealm} from './storages/Realm'; 13 | import {getFromSQLite} from './storages/SQLite'; 14 | import {getFromWatermelonDB} from './storages/WatermelonDB'; 15 | import {getFromMMKVEncrypted} from './storages/MMKVEncrypted'; 16 | import {getFromExpoSecureStorage} from "./storages/ExpoSecureStorage"; 17 | 18 | declare global { 19 | const performance: {now: () => number}; 20 | } 21 | 22 | const iterations = 1000; 23 | 24 | async function benchmark( 25 | label: string, 26 | fn: () => unknown | Promise, 27 | ): Promise { 28 | try { 29 | console.log(`Starting Benchmark "${label}"...`); 30 | const start = performance.now(); 31 | for (let i = 0; i < iterations; i++) { 32 | const r = fn(); 33 | if (r instanceof Promise) { 34 | await r; 35 | } 36 | } 37 | const end = performance.now(); 38 | const diff = end - start; 39 | console.log(`Finished Benchmark "${label}"! Took ${diff.toFixed(4)}ms!`); 40 | return diff; 41 | } catch (e) { 42 | console.error(`Failed Benchmark "${label}"!`, e); 43 | return 0; 44 | } 45 | } 46 | 47 | async function waitForGC(): Promise { 48 | // Wait for Garbage Collection to run. We give a 500ms delay. 49 | return new Promise(r => setTimeout(r, 500)); 50 | } 51 | const App = () => { 52 | const isDarkMode = useColorScheme() === 'dark'; 53 | 54 | const runBenchmarks = useCallback(async () => { 55 | console.log('Running Benchmark in 3... 2... 1...'); 56 | await waitForGC(); 57 | await benchmark('MMKV ', getFromMMKV); 58 | await waitForGC(); 59 | await benchmark('MMKV Encrypt ', getFromMMKVEncrypted); 60 | await waitForGC(); 61 | await benchmark('AsyncStorage ', getFromAsyncStorage); 62 | await waitForGC(); 63 | await benchmark('Expo Secure Storage ', getFromExpoSecureStorage); 64 | await waitForGC(); 65 | await benchmark('React Native Keychain', getFromReactNativeKeychain); 66 | await waitForGC(); 67 | await benchmark('SQLite ', getFromSQLite); 68 | await waitForGC(); 69 | await benchmark('RealmDB ', getFromRealm); 70 | await waitForGC(); 71 | await benchmark('WatermelonDB ', getFromWatermelonDB); 72 | }, []); 73 | 74 | return ( 75 | 76 | 77 |