您的位置:首页 > 移动开发 > Android开发

WHY YOU SHOULDN'T USE GETTERS AND SETTERS ON ANDROID

2016-12-09 16:12 639 查看
This post is the chapter one of a series called Applying the concepts of mechanical sympathy to your Android code.

I was never a fan of the accessor pattern in
object-oriented programming. Its "getters" and "setters" always were a big annoyance to me. It seemed to me - as a programmer - that they were only an overhead to my coding. Every time I wrote or used them, I felt like I was wasting my time.

I always used - and am a big supporter of - PODs when writing code. Especially
if my class represents some kind of serialisable data, such as a JSON in a web service.

But this always was a personal matter. I never had any proof that they were bad. But recently I started talking more closely to the compiler and found the proof: getters and setters are evil, and you shouldn't use them (at least on Android).
Here's why:


TOOLS, ENVIRONMENT AND PROCESS

To make this proof of concept, I used Delta to do the microbenchmarking. Every task was
executed a billon (1,000,000,000) times, after a warmup phase of a million (1,000,000) cycles.

I measured two blocks of code: one that accesses a String field directly in a class, and other that uses getters and setters to access it.

I ran the code blocks using two devices, both fresh from a boot:
Samsung i5500, running Android 2.1-update1. This device has no JIT.
I chose to use a low-end phone to increase the contrast on the results;
LG Nexus 4, running Android 4.2.2. At the time this article was written
this was the highest-end Android device available.

Then, I decompiled the Android code to read the assembly-like
syntax (Smali).


CODE SAMPLE

Without further ado, let's see the code I used for benchmarking. I stripped out all "non-important" code, but you can see the full project here.
The 
Model
s are the classes that contain the String attributes, and the 
Benchmark
s
are the code that will be measured.


ACCESSOR PATTERN:

ModelGetSet
:
public class ModelGetSet {

private String myString;

public String getMyString() {
return myString;
}

public void setMyString(String _string) {
myString = _string;
}
}


BenchmarkGetSet
:
public ModelGetSet getModel() {
return mModel;
}

protected Object task() {

ModelGetSet model = getModel();
model.setMyString("Am I slow?");
return model.getMyString();
}


POD PATTERN:

ModelPojo
:
public class ModelPojo {

public String myString;
}


BenchmarkPojo
:
protected Object task() {

ModelPojo model = mModel;
model.myString = "Am I slow?";
return model.myString;
}


RESULTS

Low-end device (Samsung i5500):
BenchmarkGetSet
: average of 1635,164792 nanoseconds per task;
BenchmarkPojo
: average of 782,650153 nanoseconds per task.

High-end device (LG Nexus 4):
BenchmarkGetSet
: average of 72.501145 nanoseconds per task; 

BenchmarkPojo
: average of 46.659301 nanoseconds per task.
Ie, the POD approach ran 2x faster without a JIT and 1.5x faster with a JIT!


WHY?

To understand such discrepancy between code apparently so similar, let's put on our x-ray goggles and take a look on the guts of the Java code. The language below is called Smali:


ACCESSOR PATTERN:

ModelGetSet
:
# instance fields
.field private myString:Ljava/lang/String;

# virtual methods
.method public getMyString()Ljava/lang/String;
.registers 2

iget-object v0, p0, Lio/leocad/deltaexample/core/ModelGetSet;->myString:Ljava/lang/String;

return-object v0
.end method

.method public setMyString(Ljava/lang/String;)V
.registers 2

iput-object p1, p0, Lio/leocad/deltaexample/core/ModelGetSet;->myString:Ljava/lang/String;

return-void
.end method


BenchmarkGetSet
:
.method public getModel()Lio/leocad/deltaexample/core/ModelGetSet;
.registers 2

iget-object v0, p0, Lio/leocad/deltaexample/core/BenchmarkGetSet;->mModel:Lio/leocad/deltaexample/core/ModelGetSet;

return-object v0
.end method

.method protected task()Ljava/lang/Object;
.registers 3

invoke-virtual {p0}, Lio/leocad/deltaexample/core/BenchmarkGetSet;->getModel()Lio/leocad/deltaexample/core/ModelGetSet;

move-result-object v0

const-string v1, "Am I slow?"

invoke-virtual {v0, v1}, Lio/leocad/deltaexample/core/ModelGetSet;->setMyString(Ljava/lang/String;)V

invoke-virtual {v0}, Lio/leocad/deltaexample/core/ModelGetSet;->getMyString()Ljava/lang/String;

move-result-object v1

return-object v1
.end method


The task() method of this class contains 14 16-bit code units. Futhermore, it has 3 calls to 
invoke-virtual
 (and its 
move-result-*
 counterparts),
which is a very expensive instruction.

Other interesting things: each one of the accessor methods (
getMyString()
setMyString()
 and 
getModel()
)
needs 2 registers. This requires memory allocation. And all of them make a call to 
iget-object
 or 
iput-object
.

In other words; every time we try to access a field through a getter or a setter, we need to:
Jump to a block of code in the class that contains this method (using 
invoke-virtual
); 
Allocate memory to the registers used by this method; 
Access the field (using 
iget-object
 and 
iput-object
); 
Jump to the code block we were before to store the result (
move-result-object
).


POD PATTERN:

ModelPojo
:
# instance fields
.field public myString:Ljava/lang/String;


BenchmarkPojo
:
.method protected task()Ljava/lang/Object;
.registers 3

iget-object v0, p0, Lio/leocad/deltaexample/core/BenchmarkPojo;->mModel:Lio/leocad/deltaexample/core/ModelPojo;

const-string v1, "Am I slow?"

iput-object v1, v0, Lio/leocad/deltaexample/core/ModelPojo;->myString:Ljava/lang/String;

iget-object v1, v0, Lio/leocad/deltaexample/core/ModelPojo;->myString:Ljava/lang/String;

return-object v1
.end method


See how much smaller is the code? The 
task()
 method of this approach takes only 9 16-bit code units. And, most importantly,
has no calls to 
invoke-virtual
 or 
move-result
.
Instead, it calls directly 
iget-object
 and 
iput-object
.
Ie, it does only the item 3 of the list above. That simple.


CONCLUSION

It's very important to use design patterns, especially on large projects with large teams. But some of them are just archaic, and need to be redesigned.

I'm not saying here that you should never use the accessor pattern. In some cases (like protecting, limiting or formatting the field value), it can be extremely useful. But in most of the cases, it's just bad.

But to understand why it's bad, you need mechanical sympathy. And then you can go from a bragging gossip to a respectable scholar.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: