2014-01-27 15 views
11

考虑类别Foo您是否缓存本地变量中的属性?

public class Foo { 
    private double size; 

    public double getSize() { 
     return this.size; // Always O(1) 
    } 
} 

Foo有一个名为大小属性,它被频繁访问,但从来没有修改,由给定的方法。我一直使用任何方法在不止一次访问变量时缓存一个变量的属性,因为“有人告诉我这样”而没有多少考虑。即

public void test(Foo foo) { 
    double size = foo.getSize(); // Cache it or not? 
    // size will be referenced in several places later on. 
} 

这是值得吗,还是矫枉过正?

如果我不缓存它,现代编译器是否足够聪明,可以自行缓存它?

+2

当我这样做,我这样做是为了便于阅读,而不是性能。 – yshavit

+0

取决于你实现size()方法的复杂性,例如在ArrayList或String的情况下它是O(1),所以它不值得它缓存 –

+2

只需更改'double size = foo。getSize();'到'double fooSize = foo.getSize();'。 –

回答

15

两个因素(排名不分先后),我决定是否要通过调用返回的值存储到“get()方法”时考虑:的

  1. 性能get()方法 - 除非API指定,否则除非调用代码与被调用方法紧密耦合,否则get()方法的性能无法保证。代码在现在的测试中可能没有问题,但如果get()方法将来会执行更改或者测试不能反映真实世界的情况,则代码可能会变得更糟。 A - (只有在容器一千对象例如,测试时,一个真实世界的容器可能有千万)用在for循环中,get()方法会在每次迭代

  2. 可读性之前调用变量可以被赋予一个特定的描述性名称,以通过内联调用get()方法可能不明确的方式澄清其用法和/或含义。不要低估这对审查和维护代码的价值。

  3. 线程安全 - 如果另一个线程在调用方法正在做它的事情时修改对象,则get()方法返回的值是否可能会更改?这种变化是否应该反映在调用方法的行为中?

关于编译器是否自己缓存它的问题,我会推测并说,在大多数情况下,答案必须是“否”。编译器可以安全地这样做的唯一方法是,如果它可以确定get()方法在每次调用时都会返回相同的值。如果get()方法本身被标记为final,并且它所做的只是返回一个常量(即对象或基元也标记为“final”),则只能保证这一点。我不确定,但我认为这可能不是编译器所苦恼的场景。 JIT编译器有更多的信息,因此可以有更多的灵活性,但是你不能保证某些方法会被JIT处理。

结论,不要担心编译器可能做什么。缓存get()方法的返回值可能是大部分时间做的正确的事情,并且很少会(i。e几乎从来没有)是不对的。喜欢写代码快速(est)和浮华的代码是可读和正确的。

+1

通常,在堆栈上缓存是一种很好的做法。递归调用会在堆栈上占用更多的内存,这对于非常特定的情况可能是个问题,但为了避免“堆栈溢出”错误,应避免深度递归本身。 – jbaliuka

5

我不知道是否有“正确”的答案,但我会保留本地副本。

在你的例子中,我可以看到getSize()是微不足道的,但在真实代码中,我并不总是知道它是否微不足道;即使今天是微不足道的,我也不知道有人会不会来改变方法,以便在未来某个时候变得不平凡。

3

最大的因素是性能。如果这是一个简单的操作,不需要大量的CPU周期,我会说不要缓存它。但是,如果您不断需要对不会更改的数据执行昂贵的操作,那么必须对其进行缓存。例如,在我的应用程序中,当前登录的用户是以JSON格式在每个页面上序列化的,序列化操作相当昂贵,因此为了提高性能,我现在在登录时序列化用户一次,然后使用序列化版本把JSON放在页面上。在此之前和之后,制成性能明显改善:

//之前

public User(Principal principal) { 
    super(principal.getUsername(), principal.getPassword(), principal.getAuthorities()); 
    uuid   = principal.getUuid(); 
    id    = principal.getId(); 
    name   = principal.getName(); 
    isGymAdmin  = hasAnyRole(Role.ROLE_ADMIN); 
    isCustomBranding= hasAnyRole(Role.ROLE_CUSTOM_BRANDING); 
    locations.addAll(principal.getLocations()); 
} 
public String toJson() { 
    **return JSONAdapter.getGenericSerializer().serialize(this);** 
} 

//后

public User(Principal principal) { 
    super(principal.getUsername(), principal.getPassword(), principal.getAuthorities()); 
    uuid   = principal.getUuid(); 
    id    = principal.getId(); 
    name   = principal.getName(); 
    isGymAdmin  = hasAnyRole(Role.ROLE_ADMIN); 
    isCustomBranding= hasAnyRole(Role.ROLE_CUSTOM_BRANDING); 
    locations.addAll(principal.getLocations()); 
    **json = JSONAdapter.getGenericSerializer().serialize(this);** 
} 
public String toJson() { 
    return json; 
} 

用户对象没有setter方法,就没有办法了数据永远不会改变,除非用户注销并返回,所以在这种情况下,我认为缓存值是安全的。

1

IMO,如果你真的担心的表现,这是一个有点矫枉过正或广泛的,但有几个方法,以确保变量“缓存”通过你的虚拟机,

首先,你可以创建最终结果的静态变量(根据您的示例1或0),因此只有一个副本存储为整个类,那么您的局部变量只是一个布尔值(仅使用1位),但仍然保持结果值为double同时,也许你可以使用int,如果仅仅是0或1)

private static final double D_ZERO = 0.0; 
private static final double D_ONE = 1.0; 

private boolean ZERO = false; 

public double getSize(){ 
    return (ZERO ? D_ZERO : D_ONE); 
} 

或者,如果你能设置大小的类,你可以去W的初始化第i个这个,你可以通过构造函数中设置的最后一个变量和静态的,但由于这是一个局部变量,你可以用构造函数去:

private final int SIZE; 
public foo(){ 
    SIZE = 0; 
} 

public double getSize(){ 
    return this.SIZE; 
} 

这可以通过foo.getSize()

2

访问如果价值大小每次通过循环遍历一个数组来计算并因此不是O(1),缓存该值在性能方面具有明显的优势。但是因为大小Foo预计不会在任何时候改变,并且它是O(1),缓存该值主要有助于可读性。我建议继续缓存该值,因为可读性往往比现代计算系统中的性能更为关注。

1

在我的代码中,如果getSize()方法耗时或者 - 更经常 - 结果用于多或少复杂的表达式中,我会缓存它。

例如,如果计算(对我来说)一个从尺寸

int offset = fooSize * count1 + fooSize * count2; 

更容易阅读抵消比

int offset = foo.getSize() * count1 + foo.getSize() * count2; 
相关问题