diff --git a/apps/native-component-list/assets/expo-blob/performance-test-1mb.mp3 b/apps/native-component-list/assets/expo-blob/performance-test-1mb.mp3 new file mode 100644 index 00000000000000..e9cc74d0edd19a Binary files /dev/null and b/apps/native-component-list/assets/expo-blob/performance-test-1mb.mp3 differ diff --git a/apps/native-component-list/assets/expo-blob/performance-test-1mb.mp4 b/apps/native-component-list/assets/expo-blob/performance-test-1mb.mp4 new file mode 100644 index 00000000000000..7cc1cc31dadb22 Binary files /dev/null and b/apps/native-component-list/assets/expo-blob/performance-test-1mb.mp4 differ diff --git a/apps/native-component-list/assets/expo-blob/performance-test-2mb.bmp b/apps/native-component-list/assets/expo-blob/performance-test-2mb.bmp new file mode 100644 index 00000000000000..6c9d28a56ea3fb Binary files /dev/null and b/apps/native-component-list/assets/expo-blob/performance-test-2mb.bmp differ diff --git a/apps/native-component-list/src/screens/Blob/BlobScreen.tsx b/apps/native-component-list/src/screens/Blob/BlobScreen.tsx index 8201da89a6b7a5..868e4c8410ce7f 100644 --- a/apps/native-component-list/src/screens/Blob/BlobScreen.tsx +++ b/apps/native-component-list/src/screens/Blob/BlobScreen.tsx @@ -50,6 +50,14 @@ export const BlobScreens = [ return optionalRequire(() => require('./expo-blob/BlobBytesScreen')); }, }, + { + name: 'Blob Performance Test', + route: 'expo-blob/performance', + options: {}, + getComponent() { + return optionalRequire(() => require('./expo-blob/BlobPerformanceTestScreen')); + }, + }, ]; export default function BlobScreen() { diff --git a/apps/native-component-list/src/screens/Blob/expo-blob/BlobPerformanceTestScreen.tsx b/apps/native-component-list/src/screens/Blob/expo-blob/BlobPerformanceTestScreen.tsx new file mode 100644 index 00000000000000..ca6f44190f5c00 --- /dev/null +++ b/apps/native-component-list/src/screens/Blob/expo-blob/BlobPerformanceTestScreen.tsx @@ -0,0 +1,287 @@ +import { Asset } from 'expo-asset'; +import { ExpoBlob } from 'expo-blob'; +import { useState } from 'react'; +import { View, Text, StyleSheet, Button, ScrollView } from 'react-native'; + +import HeadingText from '../../../components/HeadingText'; +import MonoText from '../../../components/MonoText'; +import { Page } from '../../../components/Page'; + +type PerformanceTestData = { + key: string; + blobOperation: () => Promise; + expoBlobOperation: () => Promise; + title: string; + iterations: number; +}; + +function blobToBase64(blob: Blob) { + return new Promise((resolve) => { + const reader = new FileReader(); + reader.onloadend = () => { + const result = reader.result; + if (typeof result === 'string') { + resolve(result); + } + }; + reader.readAsDataURL(blob); + }); +} + +const performanceTest: PerformanceTestData[] = [ + { + key: 'basic-test', + blobOperation: async () => { + const T0 = performance.now(); + const blob = new Blob(['abcd'.repeat(50000)]); + blob.slice(0, 500000).slice(0, 50_000).slice(0, 40_000).slice(0, 30_000); + const T1 = performance.now(); + return T1 - T0; + }, + expoBlobOperation: async () => { + const T0 = performance.now(); + const blob = new ExpoBlob(['abcd'.repeat(50000)]); + blob.slice(0, 500000).slice(0, 50_000).slice(0, 40_000).slice(0, 30_000); + const T1 = performance.now(); + return T1 - T0; + }, + title: 'String Test', + iterations: 100, + }, + { + key: 'bmp-file-test', + blobOperation: async () => { + const asset = Asset.fromModule( + require('../../../../assets/expo-blob/performance-test-2mb.bmp') + ); + await asset.downloadAsync(); + const uri = asset.localUri || asset.uri; + const response = await fetch(uri); + const blobResponse = await response.blob(); + const base64 = await blobToBase64(blobResponse); + + const T0 = performance.now(); + const blob = new Blob([base64]); + blob.slice(0, 1000); + const T1 = performance.now(); + return T1 - T0; + }, + expoBlobOperation: async () => { + const asset = Asset.fromModule( + require('../../../../assets/expo-blob/performance-test-2mb.bmp') + ); + await asset.downloadAsync(); + const uri = asset.localUri || asset.uri; + const response = await fetch(uri); + const blobResponse = await response.blob(); + const base64 = await blobToBase64(blobResponse); + + const T0 = performance.now(); + const blob = new ExpoBlob([base64]); + blob.slice(0, 1000); + const T1 = performance.now(); + return T1 - T0; + }, + title: 'File Test (2MB BMP)', + iterations: 5, + }, + { + key: 'audio-file-test', + blobOperation: async () => { + const asset = Asset.fromModule( + require('../../../../assets/expo-blob/performance-test-1mb.mp3') + ); + await asset.downloadAsync(); + const uri = asset.localUri || asset.uri; + const response = await fetch(uri); + const blobResponse = await response.blob(); + const base64 = await blobToBase64(blobResponse); + + const T0 = performance.now(); + const blob = new Blob([base64]); + blob.slice(0, 1000); + const T1 = performance.now(); + return T1 - T0; + }, + expoBlobOperation: async () => { + const asset = Asset.fromModule( + require('../../../../assets/expo-blob/performance-test-1mb.mp3') + ); + await asset.downloadAsync(); + const uri = asset.localUri || asset.uri; + const response = await fetch(uri); + const blobResponse = await response.blob(); + const base64 = await blobToBase64(blobResponse); + + const T0 = performance.now(); + const blob = new ExpoBlob([base64]); + blob.slice(0, 1000); + const T1 = performance.now(); + return T1 - T0; + }, + title: 'File Test (1MB Audio)', + iterations: 25, + }, + { + key: 'video-file-test', + blobOperation: async () => { + const asset = Asset.fromModule( + require('../../../../assets/expo-blob/performance-test-1mb.mp4') + ); + await asset.downloadAsync(); + const uri = asset.localUri || asset.uri; + const response = await fetch(uri); + const blobResponse = await response.blob(); + const base64 = await blobToBase64(blobResponse); + + const T0 = performance.now(); + const blob = new Blob([base64]); + blob.slice(0, 1000); + const T1 = performance.now(); + return T1 - T0; + }, + expoBlobOperation: async () => { + const asset = Asset.fromModule( + require('../../../../assets/expo-blob/performance-test-1mb.mp4') + ); + await asset.downloadAsync(); + const uri = asset.localUri || asset.uri; + const response = await fetch(uri); + const blobResponse = await response.blob(); + const base64 = await blobToBase64(blobResponse); + + const T0 = performance.now(); + const blob = new ExpoBlob([base64]); + blob.slice(0, 1000); + const T1 = performance.now(); + return T1 - T0; + }, + title: 'File Test (1MB Video)', + iterations: 25, + }, +]; + +type ArrayBufferExampleItemProps = { + example: PerformanceTestData; + result: { + blobTime: number; + expoBlobTime: number; + } | null; + onEvaluate: (example: PerformanceTestData) => void; +}; + +function ArrayBufferExampleItem({ example, result, onEvaluate }: ArrayBufferExampleItemProps) { + let comparison = null; + if (result) { + const { blobTime, expoBlobTime } = result; + if (blobTime < expoBlobTime) { + const diff = expoBlobTime - blobTime; + const percent = (100 * diff) / expoBlobTime; + comparison = `Blob is ${percent.toFixed(6)}% (${diff.toFixed(6)} ms) faster`; + } else if (expoBlobTime < blobTime) { + const diff = blobTime - expoBlobTime; + const percent = (100 * diff) / blobTime; + comparison = `ExpoBlob is ${percent.toFixed(6)}% (${diff.toFixed(6)} ms) faster`; + } else { + comparison = 'Both are equally fast'; + } + } + return ( + + {example.title} + + {!result &&