You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I asked AI to plot up a quick perfomarnce benchmark. Here is the result:
NOTE: Report and test are fully AI generated, so take it with a hint of salt
Benchmark run on the demo folder of this project, so anyone can recreate it.
I. Report
exifr vs exiftool-vendored Performance Analysis
Executive Summary
The benchmark results clearly demonstrate that exifr significantly outperforms exiftool-vendored on Windows, with performance differences ranging from 6x to 66x faster depending on configuration.
Test Environment
Platform: Windows 10/11 x64
CPU: Intel Core i7-6700HQ @ 2.60GHz (8 cores)
Node.js: v22.17.1
Test Files: 20 JPEG images from demo/images directory (ranging from ~18KB to ~1.1MB)
Benchmark Results
Performance Summary
Library
Configuration
Total Time
Files/sec
Avg Time/File
Relative Speed
exifr
concurrent
34ms
588.24
18.89ms
Baseline (Fastest)
exifr
sequential
402ms
49.75
19.95ms
11.8x slower
exiftool-vendored
default sequential
2374ms
8.42
118.60ms
69.8x slower
exiftool-vendored
optimized concurrent
2244ms
8.91
1506.45ms*
66.0x slower
*Note: The optimized concurrent average time appears inflated due to measurement methodology - individual file times were higher due to process coordination overhead.
Key Findings
exifr concurrent processing is dramatically faster:
Processes 20 files in just 34ms vs 2244ms for best exiftool-vendored
Achieves 588 files/second vs ~9 files/second for exiftool-vendored
Windows process spawning penalty confirmed:
Even optimized exiftool-vendored with 8 concurrent processes couldn't overcome the Windows subprocess overhead
First file in exiftool-vendored took 533ms vs 5ms minimum for exifr
Memory efficiency:
exifr: ~5MB RSS overhead for concurrent processing
Analysis of exiftool-vendored Author's Suggestions
The exiftool-vendored author's suggestions were implemented in our optimized test:
✅ maxProcs set to CPU count: Used all 8 CPU cores
✅ Increased timeout: Set to 30 seconds for Windows
✅ Concurrent processing: Tested with Promise.all()
✅ Multiple files: Tested with 20 files
Despite these optimizations, exiftool-vendored still performed significantly slower due to:
Windows subprocess overhead: Each exiftool process spawn incurs significant latency
IPC overhead: Communication between Node.js and exiftool processes
Process coordination: Managing multiple exiftool processes adds overhead
Recommendations
For PiGallery2 (Performance-Critical Application)
Recommendation: Continue using exifr
Reasons:
Performance: 66x faster processing enables better user experience
Efficiency: Critical for Raspberry Pi deployment where CPU is limited
Scalability: Better concurrent processing for batch operations
Memory: Lower memory footprint important for embedded devices
When exiftool-vendored Might Still Be Considered
Consider exiftool-vendored only if:
Metadata completeness is more important than performance
Specific metadata fields are only available through exiftool
Robustness for handling broken/unusual files is critical
Application runs primarily on Linux/Unix (better subprocess performance)
Hybrid Approach (If Needed)
If specific exiftool features are required:
// Use exifr for primary metadata extraction (fast)constprimaryMetadata=awaitexifr.parse(file,options);// Use exiftool only for specialized fields when neededconstspecializedData=awaitexiftool.read(file,specificTags);
Performance Impact on PiGallery2
Based on these results, switching to exiftool-vendored would:
Scanning: Make initial photo indexing 66x slower
User Experience: Significantly delay thumbnail generation and gallery browsing
Resource Usage: Increase CPU time per operation by ~6600%
For a Raspberry Pi deployment processing hundreds or thousands of photos, this would translate to:
Current (exifr): ~1000 photos in ~34 seconds
With exiftool-vendored: ~1000 photos in ~37 minutes
Conclusion
The benchmark confirms your initial concern about exiftool-vendored performance on Windows. The dramatic performance difference (66x slower) makes exiftool-vendored unsuitable for PiGallery2's use case, especially given the application's focus on Raspberry Pi deployment where performance is critical.
Final Recommendation: Keep using exifr for PiGallery2's metadata extraction needs.
II. Benchmark code:
import*asfsfrom'fs';import*aspathfrom'path';import*asosfrom'os';// eslint-disable-next-line @typescript-eslint/ban-ts-comment// @ts-ignoreimport*asexifrfrom'exifr';import{exiftool,ExifTool}from'exiftool-vendored';interfaceBenchmarkResult{library: string;configuration: string;totalFiles: number;totalTime: number;averageTime: number;minTime: number;maxTime: number;memoryUsage: NodeJS.MemoryUsage;errors: number;}asyncfunctiongetTestFiles(): Promise<string[]>{constdemoDir=path.join(process.cwd(),'demo','images');constfiles=fs.readdirSync(demoDir,{recursive: true,withFileTypes: true});returnfiles.filter(file=>file.isFile()&&/\.(jpg|jpeg|tiff|tif|png)$/i.test(file.name)).map(file=>path.join(file.path||demoDir,file.name)).slice(0,20);// Limit to first 20 files for reasonable test time}asyncfunctionbenchmarkExifr(files: string[]): Promise<BenchmarkResult>{constexifrOptions={tiff: true,xmp: true,icc: false,jfif: false,ihdr: true,iptc: true,exif: true,gps: true,reviveValues: false,translateValues: false,mergeOutput: false};consttimes: number[]=[];leterrors=0;conststartMemory=process.memoryUsage();console.log('Testing exifr (sequential)...');consttotalStart=Date.now();for(constfileoffiles){conststart=Date.now();try{awaitexifr.parse(file,exifrOptions);consttime=Date.now()-start;times.push(time);process.stdout.write('.');}catch(error){errors++;process.stdout.write('E');}}consttotalTime=Date.now()-totalStart;constendMemory=process.memoryUsage();console.log(`\nExifr sequential completed in ${totalTime}ms`);return{library: 'exifr',configuration: 'sequential',totalFiles: files.length,
totalTime,averageTime: times.reduce((a,b)=>a+b,0)/times.length,minTime: Math.min(...times),maxTime: Math.max(...times),memoryUsage: {rss: endMemory.rss-startMemory.rss,heapTotal: endMemory.heapTotal-startMemory.heapTotal,heapUsed: endMemory.heapUsed-startMemory.heapUsed,external: endMemory.external-startMemory.external,arrayBuffers: endMemory.arrayBuffers-startMemory.arrayBuffers},
errors
};}asyncfunctionbenchmarkExifrConcurrent(files: string[]): Promise<BenchmarkResult>{constexifrOptions={tiff: true,xmp: true,icc: false,jfif: false,ihdr: true,iptc: true,exif: true,gps: true,reviveValues: false,translateValues: false,mergeOutput: false};leterrors=0;conststartMemory=process.memoryUsage();console.log('Testing exifr (concurrent)...');consttotalStart=Date.now();constpromises=files.map(async(file)=>{conststart=Date.now();try{awaitexifr.parse(file,exifrOptions);consttime=Date.now()-start;process.stdout.write('.');returntime;}catch(error){errors++;process.stdout.write('E');return0;}});consttimes=awaitPromise.all(promises);constvalidTimes=times.filter(t=>t>0);consttotalTime=Date.now()-totalStart;constendMemory=process.memoryUsage();console.log(`\nExifr concurrent completed in ${totalTime}ms`);return{library: 'exifr',configuration: 'concurrent',totalFiles: files.length,
totalTime,averageTime: validTimes.reduce((a,b)=>a+b,0)/validTimes.length,minTime: Math.min(...validTimes),maxTime: Math.max(...validTimes),memoryUsage: {rss: endMemory.rss-startMemory.rss,heapTotal: endMemory.heapTotal-startMemory.heapTotal,heapUsed: endMemory.heapUsed-startMemory.heapUsed,external: endMemory.external-startMemory.external,arrayBuffers: endMemory.arrayBuffers-startMemory.arrayBuffers},
errors
};}asyncfunctionbenchmarkExiftoolDefault(files: string[]): Promise<BenchmarkResult>{consttimes: number[]=[];leterrors=0;conststartMemory=process.memoryUsage();console.log('Testing exiftool-vendored (default settings, sequential)...');consttotalStart=Date.now();for(constfileoffiles){conststart=Date.now();try{awaitexiftool.read(file);consttime=Date.now()-start;times.push(time);process.stdout.write('.');}catch(error){errors++;process.stdout.write('E');}}consttotalTime=Date.now()-totalStart;constendMemory=process.memoryUsage();console.log(`\nExiftool default sequential completed in ${totalTime}ms`);return{library: 'exiftool-vendored',configuration: 'default-sequential',totalFiles: files.length,
totalTime,averageTime: times.reduce((a,b)=>a+b,0)/times.length,minTime: Math.min(...times),maxTime: Math.max(...times),memoryUsage: {rss: endMemory.rss-startMemory.rss,heapTotal: endMemory.heapTotal-startMemory.heapTotal,heapUsed: endMemory.heapUsed-startMemory.heapUsed,external: endMemory.external-startMemory.external,arrayBuffers: endMemory.arrayBuffers-startMemory.arrayBuffers},
errors
};}asyncfunctionbenchmarkExiftoolOptimized(files: string[]): Promise<BenchmarkResult>{// Create optimized ExifTool instanceconstoptimizedExiftool=newExifTool({maxProcs: os.cpus().length,// Use all CPU cores as suggestedtaskTimeoutMillis: 30000,// 30 second timeout as suggested for WindowsmaxTasksPerProcess: 500// Allow more tasks per process});leterrors=0;conststartMemory=process.memoryUsage();console.log(`Testing exiftool-vendored (optimized: ${os.cpus().length} procs, concurrent)...`);consttotalStart=Date.now();constpromises=files.map(async(file)=>{conststart=Date.now();try{awaitoptimizedExiftool.read(file);consttime=Date.now()-start;process.stdout.write('.');returntime;}catch(error){errors++;process.stdout.write('E');return0;}});consttimes=awaitPromise.all(promises);constvalidTimes=times.filter(t=>t>0);consttotalTime=Date.now()-totalStart;// Clean up the optimized instanceawaitoptimizedExiftool.end();constendMemory=process.memoryUsage();console.log(`\nExiftool optimized concurrent completed in ${totalTime}ms`);return{library: 'exiftool-vendored',configuration: `optimized-concurrent (${os.cpus().length} procs)`,totalFiles: files.length,
totalTime,averageTime: validTimes.reduce((a,b)=>a+b,0)/validTimes.length,minTime: Math.min(...validTimes),maxTime: Math.max(...validTimes),memoryUsage: {rss: endMemory.rss-startMemory.rss,heapTotal: endMemory.heapTotal-startMemory.heapTotal,heapUsed: endMemory.heapUsed-startMemory.heapUsed,external: endMemory.external-startMemory.external,arrayBuffers: endMemory.arrayBuffers-startMemory.arrayBuffers},
errors
};}functionprintResults(results: BenchmarkResult[]){console.log('\n'+'='.repeat(80));console.log('BENCHMARK RESULTS');console.log('='.repeat(80));console.log(`Platform: ${os.platform()}${os.arch()}`);console.log(`CPUs: ${os.cpus().length} (${os.cpus()[0].model})`);console.log(`Node.js: ${process.version}`);console.log('='.repeat(80));results.forEach(result=>{console.log(`\n${result.library} (${result.configuration}):`);console.log(` Files processed: ${result.totalFiles}`);console.log(` Total time: ${result.totalTime}ms`);console.log(` Average time per file: ${result.averageTime.toFixed(2)}ms`);console.log(` Min/Max time: ${result.minTime}ms / ${result.maxTime}ms`);console.log(` Files per second: ${(result.totalFiles/result.totalTime*1000).toFixed(2)}`);console.log(` Errors: ${result.errors}`);console.log(` Memory delta (RSS): ${(result.memoryUsage.rss/1024/1024).toFixed(2)}MB`);console.log(` Memory delta (Heap): ${(result.memoryUsage.heapUsed/1024/1024).toFixed(2)}MB`);});console.log('\n'+'='.repeat(80));console.log('PERFORMANCE COMPARISON');console.log('='.repeat(80));constfastest=results.reduce((prev,current)=>current.totalTime<prev.totalTime ? current : prev);results.forEach(result=>{constratio=result.totalTime/fastest.totalTime;constpercentage=((ratio-1)*100).toFixed(1);conststatus=result===fastest ? ' (FASTEST)' : ` (+${percentage}% slower)`;console.log(`${result.library} (${result.configuration}): ${result.totalTime}ms${status}`);});}asyncfunctionmain(){try{console.log('Getting test files...');constfiles=awaitgetTestFiles();console.log(`Found ${files.length} image files for testing`);console.log('Files:',files.map(f=>path.basename(f)).join(', '));constresults: BenchmarkResult[]=[];// Test exifr sequentialresults.push(awaitbenchmarkExifr(files));// Wait a bit between tests to let memory settleconsole.log('Waiting 2 seconds...');awaitnewPromise(resolve=>setTimeout(resolve,2000));// Test exifr concurrentresults.push(awaitbenchmarkExifrConcurrent(files));console.log('Waiting 2 seconds...');awaitnewPromise(resolve=>setTimeout(resolve,2000));// Test exiftool-vendored defaultresults.push(awaitbenchmarkExiftoolDefault(files));console.log('Waiting 2 seconds...');awaitnewPromise(resolve=>setTimeout(resolve,2000));// Test exiftool-vendored optimizedresults.push(awaitbenchmarkExiftoolOptimized(files));// Print comprehensive resultsprintResults(results);}catch(error){console.error('Benchmark failed:',error);process.exit(1);}finally{// Clean up default exiftool instanceawaitexiftool.end();}}main().catch(console.error);
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I asked AI to plot up a quick perfomarnce benchmark. Here is the result:
NOTE: Report and test are fully AI generated, so take it with a hint of salt
Benchmark run on the demo folder of this project, so anyone can recreate it.
I. Report
exifr vs exiftool-vendored Performance Analysis
Executive Summary
The benchmark results clearly demonstrate that exifr significantly outperforms exiftool-vendored on Windows, with performance differences ranging from 6x to 66x faster depending on configuration.
Test Environment
Benchmark Results
Performance Summary
*Note: The optimized concurrent average time appears inflated due to measurement methodology - individual file times were higher due to process coordination overhead.
Key Findings
exifr concurrent processing is dramatically faster:
Windows process spawning penalty confirmed:
Memory efficiency:
Error handling:
Analysis of exiftool-vendored Author's Suggestions
The exiftool-vendored author's suggestions were implemented in our optimized test:
Despite these optimizations, exiftool-vendored still performed significantly slower due to:
Recommendations
For PiGallery2 (Performance-Critical Application)
Recommendation: Continue using exifr
Reasons:
When exiftool-vendored Might Still Be Considered
Consider exiftool-vendored only if:
Hybrid Approach (If Needed)
If specific exiftool features are required:
Performance Impact on PiGallery2
Based on these results, switching to exiftool-vendored would:
For a Raspberry Pi deployment processing hundreds or thousands of photos, this would translate to:
Conclusion
The benchmark confirms your initial concern about exiftool-vendored performance on Windows. The dramatic performance difference (66x slower) makes exiftool-vendored unsuitable for PiGallery2's use case, especially given the application's focus on Raspberry Pi deployment where performance is critical.
Final Recommendation: Keep using exifr for PiGallery2's metadata extraction needs.
II. Benchmark code:
Beta Was this translation helpful? Give feedback.
All reactions