2014-01-27 41 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; 
相關問題