Optimizing the Netflix Android application
-
Upload
francois-goldfain -
Category
Presentations & Public Speaking
-
view
493 -
download
2
Transcript of Optimizing the Netflix Android application
Optimizing the Netflix Android Application
1
Francois Goldfain Engineering Manager
Images
2
Minimize Image Resolution! Request different images based on screen resolution, screen density and device memory"! Trade-off between quality and performance"! Storing different image sizes on your server is worth it"! Re-consider your logic and thresholds regularly"
3
86 x 120 6KB to download
20KB in heap (Kindle Fire 2011, RCA Tablet 2014)
150 x 210 10KB to download
61KB in heap (Amazon Fire Tablet 2015)
197 x 276 13KB to download
106KB in heap (Nexus 7 Tablet 2013)
Leverage WebP format! Compressed image size decreased by at least 30% with no visible quality difference"! Support for alpha & animation makes it a one-stop image format"! Supported natively since Android 4.0"! Software library available for earlier Android versions and for iOS"! Limited browser support"
4
Image Resolution: 592 x 333 File size in JPEG format: 39KB
File size in WebP: 22KB
56% reduction!
Image Disk Caching! By default, HTTP responses are not cached"
! android.net.HttpResponseCache turns on caching for all http requests (based on Cache-Control)"
! Application-level disk caching allows for greater flexibility"
! Netflix Disk Cache based on Volley"○ Max size varies with device disk space: Between 5MB and 25MB"○ Store cache files under Context.getCacheDir() "○ Least-Recently-Used (LRU) for images, App Session life cycle for other short lived assets"○ Ignore hostname part of URL for images: "http://cdn3.nflximg.net/ipl/13071/d2b1ce2100319d06e5e7dec5423b0e72839b8479.webp
5
Bitmap Heap Caching! Decoding an image from the disk is CPU intensive"! In-memory L1 Bitmap cache needed for smooth scrolling and efficiency"! BitmapLruCache based on android.util.LruCache"○ HashMap that returns Bitmap object"○ Evict least-recently-used Bitmap when full "
! Decoded Bitmaps in heap much larger than when compressed on disk "! Large Bitmap cache when dealing with lots of images"○ 50% of Runtime.maxMemory() with android:largeHeap="true" ○ Empty cache when application is in background with Application.onTrimMemory() "
6
Use RGB 565 when possible! By default, images are decoded with Bitmap.Config.ARGB_8888 for optimal quality"○ Alpha channel only pertains to translucent images"○ Additional colors not necessary in many cases"
! Consider using RGB_565 whenever possible"○ Faster decoding"○ Only 2 bytes of heap per pixel, instead of 4"
"
7
BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); decodeOptions.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); "
RGB 565 337x599
394KB heapARGB 8888
337x599 788KB heap "
RGB 565 Not Always Right Choice8
Accentuated color banding with RGB 565
Data Requests
9
Cost Of Cellular Connection ! Networking biggest battery hog"! Minimize radio time to avoid battery drain"! Understand high cost of sending/receiving data"○ Wake-up and initialize modem"○ Open TCP connection"○ Send/receive data at full cellular power"○ Radio powered for another ~30 seconds after last request is complete"
! Worst scenario when each data request triggers full cycle
10
Batch Network Requests! Accumulate data requests in a queue and send them all together"! Pre-fetch all data that user might need in the short term"! Piggyback on other network operations to trigger your data requests"
(JobScheduler)
11
Batch Network Requests (2)12
"https://www.youtube.com/watch?v=7sKC2JBc4dY"
Batch Network Requests (3)13
Load Home Screen with No Prefetch request
Load Home Screen with Prefetch requests
Prefetch Requests
Subsequent user inputs do not trigger additional data requests
Each subsequent user input do trigger additional data request
Avoid Polling! Cause of continuous waste of bandwidth and energy"! Unnecessary load on server for nothing"! Leverage Google Cloud Messaging to trigger a refresh
14
Avoid Polling (2)15
https://www.youtube.com/watch?v=7TXIqa6Ny7Y"
Prioritize Data Requests! Assign different priorities to data requests stored in the queue"! Consider different queues for data and asset files (images…)"! Cancel/stop data requests at critical times
16
HIGH_PRIORITY
LOW_PRIORITY
Prioritize Data Requests! Assign different priorities to data requests stored in the queue"! Consider different queues for data and asset files (images…)"! Cancel/stop data requests at critical times
16
https://youtu.be/7IkILwruZLY
Adopt Efficient Response Format! Prefer binary serialization format (protobuf, flatbuffers…)""
! If using chatty format (XML, JSON), optimize parsing (no reflection).
17
Use Few Persistent TCP Connections! Cellular network performance degrades with the number of open TCP connections"○ Same bandwidth shared among more TCP connections"○ Limited throughput and increased latency"○ Energy wasted maintaining TCP connections"
18
! Solution: Use few persistent TCP connections"○ Limit number of different server endpoints"○ HTTP Pipelining with HTTP 2"○ Connection Pooling with HTTP 1.1
"! Bonus: Adjust the number of TCP connections with network type
Terminate TCP Connections! HttpUrlConnection keeps TCP connection open after request (“Keep-Alive”)"! TCP connection closed late causes further battery drain"! Trade off between reusing connections and closing them promptly to reduce energy drain
19
HttpURLConnection closeconn = (HttpURLConnection) url.openConnection(); closeconn.setRequestProperty("connection", "close"); "
CPU Thrashing
20
CPU Power States
● Busy loop prevents power management from moving CPU to low power state ● Device power management adjusts chipset power level depending on needs
21
private class VideoThread(){ public void run(){ while(playbackRunnning) { idx = mMediaCodec.dequeueInputBuffer(timeOutUs); //wait 5ms if (idx >= 0) { ...//fill input buffer mMediaCodec.queueInputBuffer(idx,…); } idx = mMediaCodec.dequeueOutputBuffer(..., timeOutUs); //wait 5ms ...//pass output buffer to renderer } } }
CPU Power States22
private class VideoInputThread(){ public void run(){ while (playbackRunning){ idx = mMediaCodec.dequeueInputBuffer(-1); //wait till buffer is available ... //fill input buffer mMediaCodec.queueInputBuffer(idx,...); } } } private class VideoOutputThread(){ public void run(){ while (playbackRunning){ idx = mMediaCodec.dequeueOutputBuffer(-1); //wait till buffer is available .... //pass output buffer to renderer } } }
● Blocking on decoder allows power management to move CPU to low power state
Sharing large buffer between C++ & Java! Passing large C++ buffer to Java byte[] involves copying data"! Consider using a direct ByteBuffer instead of byte[] when data is large
23
static jbyteArray VectorToJavaByteArray(JNIEnv* env, std::vector<unsigned char> v) { int32_t size = v.size(); unsigned char * arr = v.data(); // allocate a new Java byte[] and copy the content of c++ buffer jbyteArray jbyteArray = env->NewByteArray(size); env->SetByteArrayRegion(jbyteArray, 0, size, (const jbyte*) arr); return jbyteArray; } "static jobject VectorToJavaByteBuffer(JNIEnv* env, std::vector<unsigned char> v) { int32_t size = v.size(); unsigned char * arr = v.data(); // create a reference to Java ByteBuffer object that holds a pointer to the c++ buffer jobject buffer = env->NewDirectByteBuffer(arr, size); return buffer; }
Conclusion! Learn about mobile performance patterns""! Profile how your application uses resources"
"! Understand how technical decisions impact performance""! Balance trade-offs
24
References! Google’s Android Performance Patterns videos"
https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE""
! AT&T’s best practices"https://developer.att.com/application-resource-optimizer/docs/best-practices)"
"! Profiling Tools"○ Android Studio’s Network Traffic Tool"○ AT&T ARO (Application Resource Optimizer)"○ Charles Proxy"
25
Q & A
26