Android performance optimization: neglected optimization points

Android performance optimization: neglected optimization points

Performance optimization is a very broad topic. The blogger has always paid close attention to this aspect of learning. Performance optimization includes many aspects, such as I/O optimization, network operation optimization, memory optimization, data structure optimization, code level optimization, UI rendering optimization, CPU resource utilization optimization, exception handling optimization, etc. . .

[[181654]]

This article describes some areas that can be optimized in Android development based on the blogger's own understanding.

ArrayList and Vector

ArrayList and Vector are both Lists implemented with arrays internally. The only difference between them is the support for multi-threading. ArrayList is thread-unsafe, while Vector has synchronized most methods internally and is thread-safe. Since it is thread-safe, its performance is definitely not as good as ArrayList (of course, the idea is definitely right), but it depends on which aspect. ArrayList is definitely more efficient than Vector in operations such as add, get, and remove, but in terms of memory, Vector performs better than ArrayList. This is ultimately caused by ArrayList's expansion strategy, which will be analyzed later.

The collection that implements the RandomAccess interface is traversed using fori

Let's first talk about the traversal methods of List collections. There are three methods: foreach, iterator, and fori.

In development, when traversal is generally required, foreach is definitely the first choice because it is efficient. This point of view is correct, but it needs to be divided into different situations.

Here are three ways I tested traversing an ArrayList collection with 1 million records:

  1. long start = System.currentTimeMillis();
  2. for ( int i = 0; i < size ; i++) {
  3. data.get(i);
  4. }
  5. long end = System.currentTimeMillis();
  6. Log.v( "zxy" , "fori cost:" +( end -start));
  7.  
  8. start = System.currentTimeMillis();
  9. for ( Integer   integer : data) {
  10.  
  11. }
  12. end = System.currentTimeMillis();
  13. Log.v( "zxy" , "foreach cost:" +( end -start));
  14.  
  15. Iterator <Integer> iterator = data.iterator();
  16. start = System.currentTimeMillis();
  17. while (iterator.hasNext()){
  18. iterator.next ();
  19. }
  20. end = System.currentTimeMillis();
  21. Log.v( "zxy" , "iterator cost:" +( end -start));
  22.  
  23. 11-19 09:11:44.276 1418-1418/? V/zxy: fori cost: 30
  24. 11-19 09:11:44.380 1418-1418/? V/zxy: foreach cost: 105
  25. 11-19 09:11:44.476 1418-1418/? V/zxy: iterator cost: 95

The so-called efficient foreach is not satisfactory in traversal, while fori is the most efficient. This is because ArrayList and Vector collections are internally implemented by arrays, so the random access speed is very fast. For Lists that can be randomly accessed, JDK implements the RandomAccess interface for them, indicating that it supports fast random access.

When traversing a LinkedList collection with 10,000 entries:

  1. 11-19 09:33:23.984 1737-1737/? V/zxy: fori cost: 351
  2. 11-19 09:33:23.988 1737-1737/? V/zxy: foreach cost: 2
  3. 11-19 09:33:23.992 1737-1737/? V/zxy: iterator cost: 4

Foreach performs best. Therefore, fori is the best choice for traversal of arrays or Lists that implement the RandomAccess interface. Foreach or iterator is the best choice for traversal of collections implemented as linked lists such as LinkedList, because foreach is implemented through iterator.

We can determine which method to use for List traversal:

  1. if (list instanceof RandomAccess)
  2. {
  3. for ( int i = 0; i < list. size (); i++) {}
  4. } else {
  5. Iterator<?> iterator = list.iterator();
  6. while (iterator.hasNext()) {
  7. iterator.next ();
  8. }
  9. }

When constructing an ArrayList with known capacity, try to specify the initial size

The internal expansion strategy of ArrayList is that when the number of elements it stores exceeds its existing size, it will expand by 1.5 times the capacity. That is, if the current capacity of ArrayList is 10,000, then when it needs to store another element, that is, the 10,001th element, it will expand due to insufficient capacity. The capacity of ArrayList after expansion becomes 15,000, and one more element means 5,000 more elements of space, which is a waste of memory resources. Moreover, expansion will also cause a memory copy of the entire array, and the default size of the ArrayList collection is 10. Therefore, a reasonable setting of the ArrayList capacity can avoid the collection from expanding. The internal expansion and array copying code of ArrayList is:

  1. Object[] newArray = new Object[s +
  2. (s < (MIN_CAPACITY_INCREMENT / 2) ?
  3. MIN_CAPACITY_INCREMENT : s >> 1)];
  4. System.arraycopy(a, 0, newArray, 0, s);
  5. array = a = newArray;

The internal expansion strategy of Vector is to expand capacity on demand, with +1 each time:

  1. if (capacityIncrement <= 0) {
  2. if ((adding = elementData.length) == 0) {
  3. adding = 1;
  4. }
  5. } else {
  6. adding = capacityIncrement;
  7. }
  8.  
  9. E[] newData = newElementArray(elementData.length + adding);

Similarly, many Map collections have their own expansion strategies. For example, each time a HashMap is expanded, the new capacity is equal to the original capacity * 2. In fact, there is also an expansion strategy inside StringBuffer and StringBuilder, which we often use for string concatenation. The default is to expand the capacity to 1.5 times the original capacity.

Therefore, for those APIs that need to be expanded, if the size of the data is known in advance, it can be set in advance. This can not only avoid the waste of space caused by expansion, but also avoid internal calls to System.arraycopy() to copy large amounts of data.

If the program needs to access the List randomly by index, ArrayList and Vector should be given priority. If necessary, try not to use LinkedList.

Although ArrayList cannot compare with Vector in terms of memory, it is efficient in data operations, especially on mobile devices such as Android. It is still advisable to sacrifice a little space in exchange for time. When it comes to thread safety, use Vector.

If a method does not need to use the members of the object, then set the method to static

Calling this method statically is 15% to 20% faster than calling this method from an object, because it can be seen from the method signature that the method call will not affect the state of the object.

Clever use of final keyword

The final keyword is generally used more in defining constants and methods, and most people tend to understand final in terms of immutability, but final also plays a big role in performance optimization.

For example: static int AGE = 10; when 10 is referenced later, there will be a field search process. For the int type, it means searching the integer constant pool in the method area. For final constants, this process is omitted. For example: static final int AGE = 10; where AGE is used, 10 will be used directly instead.

However, the above optimization technique is only valid for basic types and String types, and is invalid for other reference types. However, it is still a good habit to add static final when declaring constants.

Another powerful function of the final keyword is to define final methods that are frequently used and have been determined to be final. What are the benefits of this?

Before talking about this, let's first talk about the execution process of methods in Java. When a method is called, the method will be pushed into the stack first. After execution, the method will be popped from the stack and the resources will be released. This process is actually a memory address transfer process. When the pushed method is executed, the execution address of the program is actually transferred to the memory address where the method is stored. Before doing this operation, the memory address of the original program execution must be saved. When the method is executed and popped from the stack, the program will continue to be executed according to the saved address. This process is the method calling process.

Therefore, the method calling process actually requires space and time, and the optimization of frequent calls to the same method is actually to use inlining.

Speaking of inline functions, inline functions are actually an optimization done at compile time. The compiler will directly replace the function marked as inline with the entire function body at the place where it is called. This saves the time resources consumed by function calls, but in return the target code size increases. Therefore, the inline optimization strategy actually adopts a strategy of trading space for time. For mobile terminals, the clever use of inline functions is actually very beneficial.

If a function becomes an inline function, it is defined as final. When the program is compiled, the compiler will automatically optimize the final function inline, and then the function body will be directly expanded for use when the function is called.

In summary, the more inline functions there are, the better. On the one hand, it does improve the running efficiency of our program. On the other hand, excessive use of inline functions will backfire and may make the method body of a certain method larger and larger. Moreover, for some methods with larger method bodies, the inline expansion time may exceed the method call time. Therefore, this will not only fail to improve performance, but will reduce the performance that should be there.

In summary, we can use final modifiers for methods that are frequently used, have been determined to be final, and have small method bodies to improve program performance.

Give priority to the code provided by the system rather than writing your own

The system has many very convenient built-in APIs for us to use, such as: System, Arrays, Collections, String, etc. There are many built-in method APIs, which is much more convenient than writing them by ourselves. In addition, for Android, many APIs use the underlying C/C++ implementation, so the efficiency is faster than writing them by ourselves. Similarly, for system APIs, DVM often uses inline methods to improve efficiency.

Use exceptions with caution

Using exceptions with caution does not mean not using exceptions, but rather using exceptions to perform certain operations in the program. For example, some people will interrupt certain operations by forcibly throwing exceptions. Because when an exception is thrown, the fillInStackTrace() method will be executed, which is used to readjust the stack, so it is necessary to avoid using exceptions where it is not necessary.

<<:  9 Best JavaScript Mobile App Development Frameworks

>>:  Programmers are high-level artists, not code farmers!

Recommend

How to create a bar chart in SwiftUI

Introduction A bar chart presents categories of d...

Air ticket blind box event operation routine!

As the trend of "everything is a blind box&q...

How to attract new users and retain them?

01 This morning, I eavesdropped on a two-hour MLM...

Zhong Baidi's step-by-step photography tutorial (Advanced Edition)

Zhong Baidi's step-by-step photography tutori...

Content operation: Operational strategy for the development of UGC model

Although many companies have the concept of conte...

Key technologies for implementing microservice architecture

Everyone is talking about microservice architectu...

5000 words to explain Heytea's private domain operation method

A few days ago, when I was browsing the news, I f...