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:
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).
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
are the code that will be measured.
Low-end device (Samsung i5500):
High-end device (LG Nexus 4):
Ie, the POD approach ran 2x faster without a JIT and 1.5x faster with a JIT!
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:
The task() method of this class contains 14 16-bit code units. Futhermore, it has 3 calls to
which is a very expensive instruction.
Other interesting things: each one of the accessor methods (
needs 2 registers. This requires memory allocation. And all of them make a call to
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
Allocate memory to the registers used by this method;
Access the field (using
Jump to the code block we were before to store the result (
See how much smaller is the code? The
has no calls to
Instead, it calls directly
Ie, it does only the item 3 of the list above. That simple.
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.
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 wasexecuted 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
Models are the classes that contain the String attributes, and the
Benchmarks
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-objector
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-objectand
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-virtualor
move-result.
Instead, it calls directly
iget-objectand
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.
相关文章推荐
- How to Disable System Integrity Protection on a Mac (and Why You Shouldn’t)
- (Page 2 of 3 )A walking tour of JavaBeans 2 :What JavaBeans is, how it works, and why you want to use it
- Why you should use async tasks in .NET 4.5 and Entity Framework 6
- My thoughts on AppSecDC 2009 and why you should “OWASP”
- (Page 1 of 3 )A walking tour of JavaBeans What JavaBeans is, how it works, and why you want to use it
- (Page 3 of 3 )A walking tour of JavaBeans What JavaBeans is, how it works, and why you want to use it
- Use cp and mv carefully on Solaris.
- Kernel Korner - Why and How to Use Netlink Socket
- Kernel Korner - Why and How to Use Netlink Socket
- Kernel Korner - Why and How to Use Netlink Socket
- Why you should use Test::Class
- Why you shouldn't run as admin...
- Why you shouldn't run as admin...
- native and java performance on android
- The XML Litmus Test Understanding When and Why to Use XML
- why DNS use both UDP and TCP
- 【转】周末闲谈:C and C++, why use c++?
- How to use Trusted Connection when SQL server and web Server are on two separate machines.
- [导入][链接]Five reasons why you should never use PostgreSQL
- Who do you think is the best coder and why?