WHY YOU SHOULDN'T USE GETTERS AND SETTERS ON ANDROID

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 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-object or iput-object.

In other words; every time we try to access a field through a getter or a setter, we need to:

  1. Jump to a block of code in the class that contains this method (using invoke-virtual); 
  2. Allocate memory to the registers used by this method; 
  3. Access the field (using iget-object and iput-object); 
  4. 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.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值