SharedPreferences is a persistence mechanism that provides a simple way to store and retrieve key-value pairs in your Android application. Since it deals with file system storage, SharedPreferences framework can have negative performance impact, especially if used incorrectly or abused.
To better understand the performance profile of SharedPreferences, I wrote a benchmark to measure and compare the speeds of SharedPreferences commit
and apply
operations on real Android devices. In this article, I’ll share and discuss the results of this benchmark.
SharedPreferences Commit vs Apply
When you write data into SharedPreferences, you can choose between commit
‘ing your changes, or apply
‘ing them:
val sharedPrefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE) sharedPrefs.edit().putString("key1", "value1").commit() sharedPrefs.edit().putString("key2", "value2").apply()
Commit
operation writes the changes to the file that backs your SharedPreferences object right away. In technical terms, we say that commit
is a blocking file system write operation. This type of operations is known to be relatively time-consuming, so there is a valid concern about the performance impact of this approach.
Apply
operation doesn’t write to the file system right away, but only stores the changes in the internal in-memory cache. Then, at a later time, these changes will be persisted by a worker thread managed by the SharedPreferences framework. Since there is no blocking interaction with the file system, apply
is faster than commit
. That’s why the official guidelines recommend using apply
over commit
, and there is a default lint warning in Android Studio that will pop up if you use commit
in your code.
The potential downside of apply
is that since it doesn’t persist the changes right away, there is a slight change of losing this state. For example, if the application crashes in the middle of a flow that modifies SharedPreferences using apply
, then, in theory, the SharedPreferences framework might not have enough time to transfer the changes from the in-memory buffer to the file system, effectively corrupting the application’s state. This can be a major issue, but, fortunately, this is a very unlikely edge case.
Performance Benchmark of Commit vs Apply
In general, the recommendation to use apply
over commit
sounds reasonable. However, I’ve never seen any performance profiling of these operations. Therefore, I decided to build a benchmark to compare these approaches in my open-sourced TechYourChance application.
The flow of the benchmark is:
- Clear SharedPreferences.
- Perform multiple consecutive edits, each time putting an entry of the form
keyN=X
into SharedPreferences (whereN
is the iteration number andX
is a constant string). - Measure the duration of each edit operation.
- Execute steps 1-3
M
times to obtain multiple measurements for averaging. - Execute steps 1-4 for
commit
andapply
methods, independently. - Compute the results.
If you install the application and run this benchmark, you’ll get a results screen like this:
The results reported by the benchmark:
- Averaged durations of each incremental edit operation.
- The coefficients of a linear fit to the averaged durations.
- The maximal duration of an edit operation.
The chart shows the averaged durations as a function of the number of entries in SharedPreferences, and it becomes immediately clear that apply
operation is indeed much faster than commit
.
In addition to the chart, we also use the linear fit’s coefficients to estimate the constant overhead associated with the respective operation, and the additional overhead for each incremental edit. The last “max” data point shows “how bad it can get” at the extreme.
Benchmark Results
I ran the benchmark on several Samsung Galaxy devices that, in my estimation, correspond to three performance profiles of Android users: average, low and very low.
The results of a single benchmark’s invocation on each device:
Commit constant [ms] | Commit increment [ns] | Commit max [ms] | Apply constant [ms] | Apply increment [ns] | Apply max [ms] | |
Samsung Galaxy S22 [average perf] | 0.61 | 1200 | 2.63 | 0.07 | 111 | 0.27 |
Samsung Galaxy S20FE [low perf] | 0.6 | 2500 | 5.12 | 0.05 | 60 | 0.21 |
Samsung Galaxy S7 [very low perf] | 7.62 | 10000 | 18.74 | 0.03 | 984 | 3.1 |
The results of the benchmark on each device varied considerably between invocations. In theory, increasing the number of iterations should help in smoothing out this variance., but, in practice, I didn’t observe this effect. I suspect that the variance is caused mostly by the factors outside of benchmark’s control, like the activity of other applications and Android OS itself.
Discussion
As expected, the performance of apply
is much better than commit
: it’s an order of magnitude faster. No surprise there.
More interestingly, the incremental overhead of each additional entry when using commit
is relatively low. Much lower than I expected, actually. Even on the weakest device, writing 100 entries of approximately 105 characters each (key + value lengths), results in addition of ~1ms to the average write duration, which constitutes about 13% of the constant overhead. This means that, for SharedPreferences of reasonable sizes (say, less than 10,000 characters), write time is dominated by the constant overhead (probably related to file system access) and not by the amount of data you store in there.
We can also see that the max write durations can be considerably longer than the average values.
Conclusion
As for the main question that I wanted to answer with this benchmark, whether the performance of commit
is acceptable for production use, my personal takeaway is that I can use commit
in my code. The reasons are as follows:
- Commit yields deterministic results.
- I mostly use SharedPreferences to store configuration entries that don’t change often.
- Most of the time, I perform just one
commit
operation. - At least some of the edits of SharedPreferences will be performed on background threads, in which case the performance difference doesn’t matter.
- On modern devices, the average performance of
commit
is acceptable even at 120Hz on the UI thread. - If the usage of
commit
will cause a skipped frame once in a while, users won’t notice that. - On very low end devices, commit can have a more pronounced effect, but these devices are relatively rare. Furthermore, users of these devices suffer from poor performance all the time, so skipping several frames once in a while won’t make much difference to their experience.
Sure enough, that’s just my personal conclusion, derived from my personal assumptions. If I’d use SharedPreferences much more often, or store megabytes of data in there, I’d probably change my mind. Therefore, I invite you to run the benchmark on devices that your users use and derive your own conclusions from it (please share your results in the comments if you do).
As an anecdotal evidence, I’ve been using commit
for years in my Settings Helper library (which is an Object-Oriented wrapper around SharedPreferences) and, so far, I haven’t had any issues with this approach. Now I finally have quantitative data to support my decision. Glad the results didn’t come in differently, because that would be embarrassing 🙂
As usual, thanks for reading and subscribe to my email list if you’d like to get notifications about new articles.
`context.getSharedPreferences(name, mode)` is loading file from disk, thus doing I/O, often on main thread.
This is where I’ve seen most performance issues related to SharedPrefs, not `commit()` vs `apply()` which is well understood by developers in my experience.
It usually triggers a town of DiskReadViolation, if you’ve enabled StrictMode.
Well, try to apply() a value to sharedpreferences and the next line read the value. You will see the value is not persisted. Using apply can cause lot of issues. Use commit() and flow.