<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Antoine Mérino</title>
    <description>The latest articles on DEV Community by Antoine Mérino (@merinorus).</description>
    <link>https://dev.to/merinorus</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2410304%2Fbc51acf6-c20b-47b9-a4d9-c23ff9f2ba77.jpeg</url>
      <title>DEV Community: Antoine Mérino</title>
      <link>https://dev.to/merinorus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/merinorus"/>
    <language>en</language>
    <item>
      <title>Building Android ROMs on limited RAM: zRAM vs. zswap comparison</title>
      <dc:creator>Antoine Mérino</dc:creator>
      <pubDate>Thu, 21 Nov 2024 17:36:40 +0000</pubDate>
      <link>https://dev.to/merinorus/building-android-roms-on-limited-ram-zram-vs-zswap-comparison-42jd</link>
      <guid>https://dev.to/merinorus/building-android-roms-on-limited-ram-zram-vs-zswap-comparison-42jd</guid>
      <description>&lt;p&gt;Building an Android ROM is a memory-intensive task. The RAM requirements to build AOSP (Android Open Source Project) have kept growing for the last 15 years:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Android 4, 8 GB of RAM is enough.&lt;/li&gt;
&lt;li&gt;From Android 6, 16 GB of RAM is recommended.&lt;/li&gt;
&lt;li&gt;From Android 10, 32 GB of RAM is recommended, although it could work with some disk swap.&lt;/li&gt;
&lt;li&gt;From Android 13, 64 GB of RAM (really?!) is recommended.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll show you how I still build ROMs within a reasonable amount of time with 16GB of RAM. Even less actually, since I'm compiling in WSL (Windows Subsystem for Linux), which is basically a Linux virtual machine running on Windows.&lt;/p&gt;

&lt;p&gt;Spoiler: 8 GB is tight to build Android, but 16 GB is plenty. Read below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does AOSP build consume so much memory
&lt;/h2&gt;

&lt;p&gt;From my experience, building a custom ROM based on AOSP indeed requires about 30-40GB of memory. It may be more for ROMs with more requirements, such as LineageOS-based ROMs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Just lower the number of jobs!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, this doesn't work. Why? The Android build process occurs in multiple stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Build dependency analysis&lt;/em&gt;: The build system (&lt;code&gt;Soong&lt;/code&gt;) analyzes de build dependencies by parsing the &lt;code&gt;.bp&lt;/code&gt; blueprint files. A dependency graph is generated to determine the build order, with relationships between thousands of modules. As far as I know, the number of jobs doesn't matter: everything is loaded in memory, and that uses about 30 GB of memory or more with Android 14 (this includes RAM and swap). &lt;strong&gt;This step uses a &lt;em&gt;huge&lt;/em&gt; fixed amount of memory, regardless of the number of jobs&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Compilation and Linking&lt;/em&gt;: these steps can be run in parallel, so &lt;strong&gt;the number of jobs will determine how much memory will be needed&lt;/strong&gt;. From my experience, with 8 jobs, less than 30 GB are used.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Image generation&lt;/em&gt;: It is quite RAM-hungry, but I remember it consumes less than 10 GB on my build setup.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;You're telling me that if I have less than 32 GB of RAM, I'm screwed?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No, of course not. While you may theoretically need 30 GB, you can still compile AOSP smoothly with just 16 GB of RAM, or even less.&lt;/p&gt;

&lt;h2&gt;
  
  
  Virtually increase your system memory
&lt;/h2&gt;

&lt;p&gt;If your system needs more RAM, adding more may not be an option: you are low on budget, you have maxed out the available RAM on your computer, or the RAM is soldered...&lt;/p&gt;

&lt;p&gt;Yet, Linux provides two main techniques to virtually increase system memory: swapping memory to a physical drive, or using in-memory compression.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Use a SWAP partition on a physical drive
&lt;/h3&gt;

&lt;p&gt;SWAP increases the available memory by extending it to the physical drives. For example, you can create a 30 GB SWAP partition on your SSD. I did that for months, and while it "works", it's painfully slow. Since everything must be loaded in memory and the SSD is so much slower than RAM, the slightest change needs 50 minutes on my machine for a rebuild.&lt;/p&gt;

&lt;p&gt;→ You trade SSD space and IOPS to virtually increase the memory. The SSD becomes the bottleneck, and using a hard drive instead will be significantly slower.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Enable in-memory compression (zRAM / zcache)
&lt;/h3&gt;

&lt;p&gt;Instead of swapping data into a physical drive, Linux can also use compression to store data directly in RAM. This is very interesting because you can usually achieve a 3:1 compression ratio, or even more depending on the data and the compression algorithm. Also, the swap performance is miles ahead of the SSD so you might not even notice the performance drop.&lt;br&gt;
The caveat is that the CPU must compress and decompress the memory for the application, so there is more latency and less throughput than reading for uncompressed RAM directly.&lt;br&gt;
You can choose the compression algorithm to arbitrate between speed and compression ratio.&lt;/p&gt;

&lt;p&gt;Currently, the most supported ways to enable memory compression in Linux are  &lt;strong&gt;zRAM&lt;/strong&gt; and &lt;strong&gt;zswap&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;→ You trade CPU cycles to virtually increase the memory. The CPU becomes the bottleneck.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cool. Which one should I use?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here are the main recommendations I found on the Internet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you already have a SWAP partition on your SSD, use zswap.&lt;/li&gt;
&lt;li&gt;Otherwise, use zRAM.&lt;/li&gt;
&lt;li&gt;Android ROM building guides, such as LineageOS, say that enabling zRAM can be helpful. That's also what other Android communities recommend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's compare to see if these recommendations hold.&lt;/p&gt;
&lt;h2&gt;
  
  
  Benchmark
&lt;/h2&gt;

&lt;p&gt;Here's the setup I'm using for benchmarking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hardware specs: Intel 4910MQ (2.9 GHz 4-core/8-thread CPU from 2014), 16 GB RAM, SATA SSD.&lt;/li&gt;
&lt;li&gt;Kernel: Microsoft Kernel 6.6.36, rebuilt with zswap and zram support.&lt;/li&gt;
&lt;li&gt;Compression algorithm: lz4 v1.9.4, zstd v1.5.2&lt;/li&gt;
&lt;li&gt;Android ROM: LineageOS 21 (Android 14)&lt;/li&gt;
&lt;li&gt;Build environment: WSL2 with Ubuntu 24.04.&lt;/li&gt;
&lt;li&gt;Tests performed: Soong build step, measured 3 times for accuracy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fixed parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swap on SATA SSD: 40 GB&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/proc/sys/vm/swappiness=60&lt;/code&gt; (OS default, controls how aggressively Linux uses swap space relative to RAM)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/proc/sys/vm/pagecluster=0&lt;/code&gt; (Write 4 KB pages one by one, as a SATA SSD is fast enough with 4K reads and writes)&lt;/li&gt;
&lt;li&gt;For zswap: &lt;code&gt;zsmalloc&lt;/code&gt; allocator to fully benefit from lz4 and zstd compression ratio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each result is the mean of three runs of the following script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;NB_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8  &lt;span class="c"&gt;# After verification, this has no impact on the Soong analysis stage.&lt;/span&gt;

&lt;span class="c"&gt;# Log file to store memory usage&lt;/span&gt;
&lt;span class="nv"&gt;MEMORY_LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"memory_use.log"&lt;/span&gt;

&lt;span class="c"&gt;# Monitor the device where swap is written&lt;/span&gt;
&lt;span class="nv"&gt;DEVICE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sdb"&lt;/span&gt;

&lt;span class="c"&gt;# Return the total RAM + Swap use in MB&lt;/span&gt;
get_total_memory_use&lt;span class="o"&gt;(){&lt;/span&gt;
    free | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/^Mem:/ {ram_used=$3} /^Swap:/ {swap_used=$3; total_used=(ram_used + swap_used) / 1024; print total_used}'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Function to monitor memory usage&lt;/span&gt;
monitor_memory&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
        &lt;/span&gt;get_total_memory_use &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MEMORY_LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;sleep &lt;/span&gt;1
    &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;source &lt;/span&gt;build/envsetup.sh
breakfast redfin  &lt;span class="c"&gt;# redfin is Google Pixel 5. Could be any other device.&lt;/span&gt;

&lt;span class="c"&gt;# Add a dummy comment to the Android.bp file to trigger a new Soong analysis&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"// dummy comment"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; Android.bp

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"System memory use before benchmark: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;get_total_memory_use&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; MB (RAM + Swap)"&lt;/span&gt;

&lt;span class="c"&gt;# Start memory monitoring in the background&lt;/span&gt;
monitor_memory &amp;amp;

&lt;span class="c"&gt;# Store the PID of the monitoring process&lt;/span&gt;
&lt;span class="nv"&gt;MONITOR_PID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$!&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y-%m-%d %H:%M:%S'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;: Starting benchmark

&lt;span class="c"&gt;# Get initial SSD swap partition write stats&lt;/span&gt;
&lt;span class="nv"&gt;initial_write&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;iostat &lt;span class="nt"&gt;-m&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DEVICE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'$1 == dev {print $6}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Run the build. The "nothing" target is to do the Soong step only.&lt;/span&gt;
/usr/bin/time &lt;span class="nt"&gt;-v&lt;/span&gt; bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"source build/envsetup.sh &amp;amp;&amp;amp; m -j&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NB_JOBS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; nothing"&lt;/span&gt;

&lt;span class="c"&gt;# Stop the memory monitoring&lt;/span&gt;
&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_PID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_PID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null

&lt;span class="nv"&gt;current_write&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;iostat &lt;span class="nt"&gt;-m&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DEVICE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'$1 == dev {print $6}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;write_mb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;current_write &lt;span class="o"&gt;-&lt;/span&gt; initial_write&lt;span class="k"&gt;))&lt;/span&gt;

&lt;span class="nv"&gt;max_mem_used&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'NR==1 {max=$1} $1&amp;gt;max {max=$1} END {print max}'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MEMORY_LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Maximum memory usage during build (RAM + Swap): &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;max_mem_used&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; MB"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Total MB Written to &lt;/span&gt;&lt;span class="nv"&gt;$DEVICE&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="nv"&gt;$write_mb&lt;/span&gt;&lt;span class="s2"&gt; MB"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y-%m-%d %H:%M:%S'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: End of benchmark&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SSD Swap performance
&lt;/h3&gt;

&lt;p&gt;Let's start with SSD swap only. Here is the time (hh:mm:ss) to build the dependency graph (Soong step only) for Lineageos 21 (Android 14), with, if any, the amount of memory swapped out to the SSD during the build:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Compression settings \ System RAM&lt;/th&gt;
&lt;th&gt;7GB RAM&lt;/th&gt;
&lt;th&gt;12GB RAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No zRAM, no zswap&lt;/td&gt;
&lt;td&gt;2:16:55 / 224 GB&lt;/td&gt;
&lt;td&gt;2:00:37 / 216 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the time it took on my machine... for the graph dependency analysis only. Note how little difference between 7 GB and 12 GB of RAM! The majority of the build time is dedicated to memory swapping. Do this every day and your SSD will eventually die.&lt;/p&gt;

&lt;p&gt;Only the memory swapped out to the SSD is measured: the other writes, such as the build output or other processes, are excluded from the results.&lt;/p&gt;

&lt;h3&gt;
  
  
  zRAM performance
&lt;/h3&gt;

&lt;p&gt;Now, let's try with zRAM. zRAM acts as a compressed swap partition stored directly in RAM, reducing or eliminating the need for an SSD swap partition.&lt;br&gt;
Still, I kept the SSD swap, but with a lower priority. So the system can swap out to the zRAM, until either of the two conditions is met:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The zRAM size limit is reached: the system continues by swapping out to the SSD.&lt;/li&gt;
&lt;li&gt;The zRAM size limit is not reached, but the RAM is actually full: the system is on memory pressure and the build will most probably fail.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c"&gt;# Enable in-memory compressed swap with zRAM&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;lz4 | &lt;span class="nb"&gt;tee&lt;/span&gt; /sys/block/zram0/comp_algorithm
&lt;span class="nb"&gt;echo &lt;/span&gt;40G | &lt;span class="nb"&gt;tee&lt;/span&gt; /sys/block/zram0/disksize
mkswap /dev/zram0
swapon &lt;span class="nt"&gt;-p&lt;/span&gt; 5 /dev/zram0  &lt;span class="c"&gt;# Higher swap priority than the physical disk&lt;/span&gt;

&lt;span class="c"&gt;# Change page cluster from 3 to 0. Better, as long as there is no physical disk swapping&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;0 | &lt;span class="nb"&gt;tee&lt;/span&gt; /proc/sys/vm/page-cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The main settings to adjust are the zRAM device size and the compression algorithm. Here are the results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Compression settings \ System RAM&lt;/th&gt;
&lt;th&gt;7GB RAM&lt;/th&gt;
&lt;th&gt;12GB RAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;24 GB zRAM [&lt;code&gt;lz4&lt;/code&gt;]&lt;/td&gt;
&lt;td&gt;1:39:11 / 126 GB&lt;/td&gt;
&lt;td&gt;18:49 / 8 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24 GB zRAM [&lt;code&gt;zstd&lt;/code&gt;]&lt;/td&gt;
&lt;td&gt;59:16 / 61 GB&lt;/td&gt;
&lt;td&gt;17:21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40 GB zRAM [&lt;code&gt;lz4&lt;/code&gt;]&lt;/td&gt;
&lt;td&gt;&lt;em&gt;29:35 (FAIL)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15:01&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40 GB zRAM [&lt;code&gt;lz4hc&lt;/code&gt;]&lt;/td&gt;
&lt;td&gt;&lt;em&gt;1:26:58 (FAIL)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;34:46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40 GB zRAM [&lt;code&gt;zstd&lt;/code&gt;]&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;34:57&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;19:08&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40 GB zRAM [&lt;code&gt;deflate&lt;/code&gt;]&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Not tested&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;33:09&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No doubt, you can achieve much faster builds with in-memory compression. Here is an explanation of the results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;24 GB of zRAM may be too low: the zRAM gets full, even if the compressed equivalent uses much less than 24 GB in RAM. So, if you set this value too low, some memory is swapped out to the SSD, making the build slower. It did work well with &lt;code&gt;zstd&lt;/code&gt; and 12 GB of RAM though, but more zRAM with &lt;code&gt;lz4&lt;/code&gt; compression was more effective.&lt;/li&gt;
&lt;li&gt;With 12 GB of RAM and more, &lt;code&gt;lz4&lt;/code&gt; is often the best choice due to its low CPU usage and high speed. While its compression ratio is inferior to &lt;code&gt;zstd&lt;/code&gt;, it leaves enough free space in RAM, making it an excellent option for systems with enough memory.&lt;/li&gt;
&lt;li&gt;With 8 GB of RAM, &lt;code&gt;zstd&lt;/code&gt; should give the best results with decent speed and a higher compression ratio, allowing to keep everything in RAM, whereas &lt;code&gt;lz4&lt;/code&gt; won't give you enough space in RAM. Either the build eventually fails, or you must use SSD swap as well. Other high compression algorithms such as &lt;code&gt;deflate&lt;/code&gt; are too CPU intensive. I didn't bother testing it with 7 GB or RAM.&lt;/li&gt;
&lt;li&gt;There is no point in setting zRAM size to absurd values such as 500 GB of RAM: the limit will never be reached, and the zRAM driver will vainly consume a bit more RAM. Just set what is needed at maximum to complete a build without swapping to disk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can check how much RAM your zRAM device is using at any time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; watch &lt;span class="nt"&gt;-n&lt;/span&gt; 5 &lt;span class="nb"&gt;sudo &lt;/span&gt;zramctl
NAME       ALGORITHM DISKSIZE  DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 lz4            40G  6,5G  1,7G  1,7G       8 &lt;span class="o"&gt;[&lt;/span&gt;SWAP]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the Soong build, you can expect a compression ratio of approximately 4:1 to 5:1 when using &lt;code&gt;lz4&lt;/code&gt;. With &lt;code&gt;zstd&lt;/code&gt;, CPU load is higher but the ratio is about 10:1!&lt;/p&gt;

&lt;p&gt;Note: I didn't enable zRAM writeback, since it is not automatic: it requires a custom service to monitor memory pressure and write data back on SSD when needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zswap performance
&lt;/h3&gt;

&lt;p&gt;Zswap works a bit differently than zRAM. It requires a swap partition (hard drive, SSD... or even zRAM!) and acts as a compressed cache in RAM. So, when the system starts to swap out, the data will be compressed and kept on an allocated space of RAM. The size is configurable: for instance, you can set 20% (&lt;code&gt;max_pool_percent=20&lt;/code&gt;) of your RAM dedicated to the compressed space.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c"&gt;# Enable in-memory compressed swap cache with zswap&lt;/span&gt;

&lt;span class="c"&gt;# The zpool allocator must be set to zsmalloc to achieve the best compression ratios&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;zsmalloc | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /sys/module/zswap/parameters/zpool
&lt;span class="nb"&gt;echo &lt;/span&gt;zstd | &lt;span class="nb"&gt;tee&lt;/span&gt; /sys/module/zswap/parameters/compressor

&lt;span class="c"&gt;# Should be very high for specific cases when a lot of RAM is needed in a short time, ie. AOSP building&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;80 | &lt;span class="nb"&gt;tee&lt;/span&gt; /sys/module/zswap/parameters/max_pool_percent

&lt;span class="c"&gt;# If the page is read back to RAM, keep it also compressed (like zRAM)&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;N | &lt;span class="nb"&gt;tee&lt;/span&gt; /sys/module/zswap/parameters/exclusive_loads

&lt;span class="c"&gt;# Change page cluster from 3 to 0. Fast as long as there is no SSD swapping&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;0 | &lt;span class="nb"&gt;tee&lt;/span&gt; /proc/sys/vm/page-cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see the results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Compression settings \ System RAM&lt;/th&gt;
&lt;th&gt;7GB RAM&lt;/th&gt;
&lt;th&gt;12GB RAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;zswap, max_pool_percent=20 [&lt;code&gt;lz4&lt;/code&gt;]&lt;/td&gt;
&lt;td&gt;59:59 / 105 GB&lt;/td&gt;
&lt;td&gt;00:25:21 / 33 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zswap, max_pool_percent=20 [&lt;code&gt;zstd&lt;/code&gt;]&lt;/td&gt;
&lt;td&gt;51:55 / 73 GB&lt;/td&gt;
&lt;td&gt;21:53 / 14 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zswap, max_pool_percent=60 [&lt;code&gt;lz4&lt;/code&gt;]&lt;/td&gt;
&lt;td&gt;44:52 / 26 GB&lt;/td&gt;
&lt;td&gt;23:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zswap, max_pool_percent=60 [&lt;code&gt;zstd&lt;/code&gt;]&lt;/td&gt;
&lt;td&gt;44:11 / 3 GB&lt;/td&gt;
&lt;td&gt;17:24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zswap, max_pool_percent=80 [&lt;code&gt;lz4&lt;/code&gt;]&lt;/td&gt;
&lt;td&gt;52:26 / 8 GB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15:50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zswap, max_pool_percent=80 [&lt;code&gt;zstd&lt;/code&gt;]&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;44:30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15:29&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The results are close to the benchmark with zRAM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A high percentage of RAM must be allocated to zswap to achieve good performance.&lt;/li&gt;
&lt;li&gt;With 12 GB of RAM, &lt;code&gt;lz4&lt;/code&gt; works great. Surprisingly, &lt;code&gt;zstd&lt;/code&gt; seems equivalent.&lt;/li&gt;
&lt;li&gt;With 8 GB of RAM &lt;code&gt;zstd&lt;/code&gt; works better and is required to avoid swapping out to the SSD.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: zswap seems to handle memory pressure better than zRAM: with 7 GB of RAM, 80% (maximum) allocated to zswap, and &lt;code&gt;lz4&lt;/code&gt; compression, it most probably swaps out to the SSD long before the allocated space is full. To achieve the same result with zRAM, you must monitor it and trigger memory recompression and/or write back manually, a script or a service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: zRAM or zswap?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Features&lt;/th&gt;
&lt;th&gt;zRAM&lt;/th&gt;
&lt;th&gt;zswap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Can work without a swap partition&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High compression ratio&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes (with zsmalloc as zpool allocator)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple compression algorithms &amp;amp; recompression&lt;/td&gt;
&lt;td&gt;yes (manual)&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write back to the SSD&lt;/td&gt;
&lt;td&gt;yes (manual)&lt;/td&gt;
&lt;td&gt;yes (automatic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compressed write back to the SSD&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both technologies offer close performances.&lt;/p&gt;

&lt;p&gt;If you can't have a swap partition, use zRAM. A lot of known devices &lt;em&gt;shouldn't&lt;/em&gt; have a physical drive as a swap: Raspberry Pi and Android devices use zRAM as swap because the Flash storage is too slow and not durable enough with repetitive write operations. Synology NAS also use zRAM.&lt;/p&gt;

&lt;p&gt;If your system already comes with a swap partition on a hard drive or an SSD, both will work, but it might be easier to use zswap. However! your kernel must support &lt;code&gt;zsmalloc&lt;/code&gt; as the zpool allocator, otherwise, you will end with a much lower compression ratio than with zRAM.&lt;/p&gt;

&lt;p&gt;Both technologies are in active development and offer similar features. I would give a slight advantage to zRAM for low-RAM machines that must also swap to a physical drive, for two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the zRAM device is full you can write back the compressed pages to the physical drive. Currently, zswap doesn't support that. When it's full, it just acts as if the system was directly swapping to the physical drive: all pages are swapped out uncompressed, same-filled pages and zero-filled pages are duplicated as they were originally in RAM, thus lowering the swap performance.&lt;/li&gt;
&lt;li&gt;zRAM allows multiple compression algorithms, which mean you can use a fast one like &lt;code&gt;lz4&lt;/code&gt;, and recompress pages with a higher compression ratio algorithm such as &lt;code&gt;zstd&lt;/code&gt; when needed. Zswap doesn't support that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two advantages come at a cost: write-back and recompression are not automatic, and you must tinker with the settings and write a dedicated service for your system to behave correctly. This goes beyond the scope of this article.&lt;/p&gt;

&lt;p&gt;For the compression algorithm: &lt;code&gt;lz4&lt;/code&gt; will offer a good compression ratio with minimal performance cost. However, if memory constraints remain an issue, you can opt for &lt;code&gt;zstd&lt;/code&gt; to free up more RAM, though it comes at the expense of higher CPU usage. It's the trade-off between speed and memory efficiency.&lt;/p&gt;

&lt;p&gt;Finally, not all kernels support both zRAM and zswap, so you may just end up using whatever is available on your system.&lt;/p&gt;

</description>
      <category>android</category>
      <category>linux</category>
    </item>
    <item>
      <title>Identifying the manufacturer of your 35mm (and APS) films</title>
      <dc:creator>Antoine Mérino</dc:creator>
      <pubDate>Wed, 21 Aug 2024 17:28:05 +0000</pubDate>
      <link>https://dev.to/merinorus/identifying-the-manufacturer-of-your-35mm-and-aps-films-36fa</link>
      <guid>https://dev.to/merinorus/identifying-the-manufacturer-of-your-35mm-and-aps-films-36fa</guid>
      <description>&lt;p&gt;In a previous &lt;a href="https://www.merinorus.com/blog/digitizing-35mm-film-archive/" rel="noopener noreferrer"&gt;article&lt;/a&gt;, I introduced how I was digitizing my 35mm film archive with my digital camera.&lt;/p&gt;

&lt;p&gt;By digitizing the pictures, I wanted to save as much information as possible, including the &lt;strong&gt;film manufacturer and type&lt;/strong&gt;, because this information impacts color post-processing.&lt;/p&gt;

&lt;p&gt;Depending on whether you own the cassette or the film roll, and if they were made after the 1980s, you will most likely find enough information thanks to the DX codes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A bit of history about the DX encoding system
&lt;/h2&gt;

&lt;p&gt;The DX (Digital Index) encoding system was introduced by Kodak in the 1980s to simplify 35 mm film handling, both for consumers and photofinishers.&lt;/p&gt;

&lt;p&gt;Three types of DX codes are used:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. DX barcode
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87px229ehibs496z1p8j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87px229ehibs496z1p8j.jpg" alt="On the left cassette, the DX CAS code. On the right one, the DX barcode." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The DX barcode is printed directly on the film cassette. &lt;a href="https://patents.google.com/patent/US5761558A" rel="noopener noreferrer"&gt;It is scanned by film-processing machines&lt;/a&gt; and contains 6 digits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1: Usually 0, but can be changed by the manufacturer to sell under another brand.&lt;/li&gt;
&lt;li&gt;2 to 5: Film manufacturer and type, which indirectly determines the developing process.&lt;/li&gt;
&lt;li&gt;6: Number of exposures (i.e., how many pictures a roll can contain).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since the digits are directly written under the barcode, this is the easiest way to retrieve the manufacturer and the film type.&lt;/p&gt;

&lt;p&gt;In the picture above, the DX barcode is 412503. With this 6-digit number, you can look for a match on &lt;a href="https://industrieplus.net/dxdatabase/rech.php?dx=412503" rel="noopener noreferrer"&gt;the Big Film Database&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: this database might be shut down in the future, but the maintainer has published the &lt;a href="https://github.com/dxdatabase/Open-source-film-database" rel="noopener noreferrer"&gt;source code&lt;/a&gt;. I am maintaining a &lt;a href="https://thebigfilmdatabase.merinorus.com/" rel="noopener noreferrer"&gt;modified version with additional features&lt;/a&gt;, though it is still experimental.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. DX CAS code on the film's cassette
&lt;/h3&gt;

&lt;p&gt;The DX CAS code is also on the film cassette, but it is used by the camera. Instead of a barcode, it consists of 12 gray (metallic, conductive) or black (insulated) squares:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 &amp;amp; 7: Reference contacts, always metallic.&lt;/li&gt;
&lt;li&gt;2 to 6: Emulsion sensitivity (ISO).&lt;/li&gt;
&lt;li&gt;8 to 10: Numbers of exposures.&lt;/li&gt;
&lt;li&gt;11 &amp;amp; 12: Exposure latitude.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The camera can automatically detect the film speed and adjust exposure. To make an analogy with modern digital cameras, it is similar to the aperture mode (A), but with fixed ISO: you set the aperture, ISO is fixed, and the camera adjusts the exposure time accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. DX film edge barcode
&lt;/h3&gt;

&lt;p&gt;If your film is already developed, there is a good chance you no longer have the cassette. In this case, you have to retrieve the information from the film roll itself. You might find the label in a human-readable format, but often, you will have to rely on the DX edge barcode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1zkyi6i2jquvtfykipo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1zkyi6i2jquvtfykipo.jpg" alt="A 35mm roll, with the DX film edge barcode " width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Like the cassette's barcode, it contains the film manufacturer and type (digits 2 to 5 of the cassette's DX barcode). This helps to automate the photofinishing process. For manually operated machines, a human-readable label might be written to help the operator, but it doesn't always give us the commercial name of the film.&lt;/p&gt;

&lt;p&gt;In the 1990s, this barcode was extended with a "half-frame" number to indicate the current frame: 1, 1A, 2, 2A, 3, 3A... They are two barcodes per frame, which ensures that at least one complete barcode remains when the film roll is cut at the end of a frame.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpsnu9v8554qes4e9scso.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpsnu9v8554qes4e9scso.jpg" alt="A 35mm roll from 1998. These wider DX edge barcodes " width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm pretty sure some specialized machine or scanners read this code for color processing, but I haven't found any application or library that can read this kind of barcode. Probably because very few people need this.&lt;/p&gt;

&lt;p&gt;In the meantime, I'll explain how to interpret this barcode manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  DX edge barcode explained
&lt;/h2&gt;

&lt;p&gt;This &lt;a href="https://patents.google.com/patent/US4965628A/en" rel="noopener noreferrer"&gt;Kodak patent&lt;/a&gt; helped me understand how to read a DX edge barcode:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbao6qh284zwfvj4m8ah3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbao6qh284zwfvj4m8ah3.png" alt="Illustration from [Kodak patent n°US4965628A](https://patents.google.com/patent/US4965628A/en), describing the fields of a DX edge film barcode." width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two main parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The top part is the &lt;em&gt;clock signal&lt;/em&gt;. It's a constant, predictable pattern that helps the machine determine where the signal starts and ends.&lt;/li&gt;
&lt;li&gt;The bottom part is the &lt;em&gt;data signal&lt;/em&gt;. It contains the actual data we need to decode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The barcode is binary: a black bar is &lt;code&gt;1&lt;/code&gt;, and a white (or empty) bar is &lt;code&gt;0&lt;/code&gt;. Let's detail each part:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx4brq419tzp3v2iuaqwr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx4brq419tzp3v2iuaqwr.png" alt="DX edge barcode fields." width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;DXN1 (red)&lt;/th&gt;
&lt;th&gt;Space&lt;/th&gt;
&lt;th&gt;DXN2 (orange)&lt;/th&gt;
&lt;th&gt;Frame number (green)&lt;/th&gt;
&lt;th&gt;Half-frame flag (cyan)&lt;/th&gt;
&lt;th&gt;Space&lt;/th&gt;
&lt;th&gt;Parity bit (rose)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1110000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;001010&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DX number part 1&lt;/strong&gt;, i.e. Product code number (7 bits): &lt;code&gt;0b1110000&lt;/code&gt; = &lt;strong&gt;112&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Blank space (1 bit)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DX number part 2&lt;/strong&gt;, i.e. Generation code (4 bits): &lt;code&gt;0b0001&lt;/code&gt; = &lt;strong&gt;1&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Frame number (6 bits): &lt;code&gt;0b001010&lt;/code&gt; = 6&lt;/li&gt;
&lt;li&gt;Half frame flag (1 bit): &lt;code&gt;0b1&lt;/code&gt; = yes (this is the second half of the frame)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Blank space (1 bit)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Parity bit (1 bit): &lt;code&gt;0b1&lt;/code&gt; = 1, which means the sum of the previous bits should be odd (&lt;code&gt;0&lt;/code&gt; is even, &lt;code&gt;1&lt;/code&gt; is odd), which is the case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The frame number was added in 1990, so older negatives have a shorter barcode without this information.&lt;/li&gt;
&lt;li&gt;If you use a half-frame camera (i.e. two 18x24 mm frames per 24x36 mm roll frame), multiply the frame number by two and add the half-frame flag. For example, frames 0, 0A, 1, 1A... -&amp;gt; half-frames 0, 1, 2, 3...&lt;/li&gt;
&lt;li&gt;When reading the barcode on a real negative, you must read on the shiny side, not the matte side. Like the machine, you can use the clock signal: the start pattern is 5 bits long, while the end pattern is 3 bits long.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We now have enough information to query our film database, which tells us this film (&lt;strong&gt;112-1&lt;/strong&gt;) is probably a &lt;a href="https://thebigfilmdatabase.merinorus.com/search?dx_full=&amp;amp;dx_number=112-1" rel="noopener noreferrer"&gt;Kodak Vericolor III, Type S&lt;/a&gt;. Very cool, isn't it?&lt;/p&gt;

&lt;p&gt;If you have a lot of negatives to scan, this can be a daunting and error-prone task. I would love to have an application that automates this process. &lt;a href="https://github.com/zxing-cpp/zxing-cpp/issues/662" rel="noopener noreferrer"&gt;I've been working on adding support in ZXing C++, an open-source library&lt;/a&gt; to implement reading DX film edge barcodes. You can check out the source code, but the updated library has not been released yet. Stay tuned!&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>opensource</category>
      <category>photography</category>
    </item>
  </channel>
</rss>
