2014-01-27 7 views
11

Betrachten Sie die Klasse Foo.Cache-Eigenschaften in lokalen Variablen?

public class Foo { 
    private double size; 

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

Foo hat eine Eigenschaft Größe genannt, die häufig zugegriffen wird, aber nie geändert, von einem bestimmten Verfahren. Ich habe immer eine Eigenschaft in einer Variablen zwischengespeichert, wenn sie in irgendeiner Methode mehr als einmal aufgerufen wurde, weil "jemand es mir gesagt hat", ohne viel darüber nachzudenken. d.h.

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

Ist es das wert, oder ein Overkill?

Wenn ich es nicht zwischenspeichern, sind moderne Compiler klug genug, um es selbst zu cachen?

+2

Berechnung Wenn ich es tue, ich tue es für die Lesbarkeit, nicht die Leistung. – yshavit

+0

hängt von der Komplexität Ihrer Implementierung der size() -Methode ab, zum Beispiel im Falle von ArrayList oder String ist es O (1), also lohnt es sich nicht –

+2

zu cachen Ändern Sie einfach 'double size = foo.getSize(); 'to' double fooSize = foo.getSize(); '. –

Antwort

15

Ein paar Faktoren (in keiner bestimmten Reihenfolge), die ich bei der Entscheidung für oder gegen den Wert von einem Anruf zu einem „get() -Methode“ zurückgegeben zu speichern:

  1. Performance der get() -Methode - Sofern die API nicht spezifiziert oder der aufrufende Code eng mit der aufgerufenen Methode gekoppelt ist, gibt es keine Garantie für die Leistung der get() -Methode. Der Code ist möglicherweise beim Testen jetzt in Ordnung, kann jedoch schlechter werden, wenn die Methoden get() in der Zukunft Änderungen vornehmen oder wenn das Testen nicht die tatsächlichen Bedingungen widerspiegelt. in einer for-Schleife Gebraucht (zB Tests mit nur tausend Objekten in einem Container, wenn ein realen Container zehn Millionen haben könnten), die Methode get() wird vor jeder Iteration

  2. Ablesbarkeit genannt werden - A Variablen können einen spezifischen und beschreibenden Namen erhalten, der ihre Verwendung und/oder Bedeutung auf eine Weise verdeutlicht, die durch Inline-Aufrufe der Methode get() nicht klar ist. Unterschätzen Sie den Wert für die Überprüfung und Pflege des Codes nicht.

  3. Thread-Sicherheit - Kann der von der Methode get() zurückgegebene Wert möglicherweise geändert werden, wenn ein anderer Thread das Objekt ändert, während die aufrufende Methode ihre Aufgabe erfüllt? Sollte sich eine solche Änderung im Verhalten der aufrufenden Methode widerspiegeln?

In Bezug auf die Frage, ob oder nicht Compiler wird es selbst cachen, werde ich und sage spekulieren, dass in den meisten Fällen die Antwort ‚Nein‘ sein. Der Compiler könnte dies nur dann sicher tun, wenn er feststellen könnte, dass die Methode get() bei jedem Aufruf den gleichen Wert zurückgibt. Und das konnte nur garantiert werden, wenn die get() - Methode selbst als final markiert wurde und nur eine Konstante zurückgegeben wurde (d. H. Ein Objekt oder Primitiv, das auch als "final" gekennzeichnet ist). Ich bin mir nicht sicher, aber ich denke, das ist wahrscheinlich kein Szenario, mit dem sich der Compiler herumschlägt. Der JIT-Compiler verfügt über mehr Informationen und könnte daher flexibler sein, aber Sie haben keine Garantie, dass eine Methode JIT-behandelt wird.

Abschließend, keine Sorge, was der Compiler tun könnte. Das Zwischenspeichern des Rückgabewertes einer get() - Methode ist wahrscheinlich die richtige Sache und wird selten (i.e fast nie) ist die falsche Sache zu tun. Bevorzugter Schreibcode, der über Code, der schnell (est) und auffällig ist, lesbar und korrekt ist.

+1

Normalerweise ist es eine gute Übung, im Stapel zu cachen. Rekursive Aufrufe verbrauchen mehr Speicher auf dem Stack und können für sehr spezielle Fälle ein Problem darstellen, aber eine tiefe Rekursion selbst sollte vermieden werden, um "Stack Overflow" -Fehler zu vermeiden. – jbaliuka

5

Ich weiß nicht, ob es eine "richtige" Antwort gibt, aber ich würde eine lokale Kopie behalten.

In Ihrem Beispiel kann ich sehen, dass getSize() trivial ist, aber im echten Code weiß ich nicht immer, ob es trivial ist oder nicht; und selbst wenn es heute trivial ist, weiß ich nicht, dass jemand nicht mitkommen und die getSize() Methode ändern wird, um es irgendwann in der Zukunft nicht-trivial zu machen.

3

Der größte Faktor wäre die Leistung. Wenn es sich um eine einfache Operation handelt, die nicht viele CPU-Zyklen erfordert, würde ich sagen, dass sie nicht zwischengespeichert wird. Aber wenn Sie ständig eine teure Operation auf Daten ausführen müssen, die sich nicht ändern, dann speichern Sie sie definitiv zwischen. Zum Beispiel wird der aktuell angemeldete Benutzer in meiner App auf jeder Seite im JSON-Format serialisiert, die Serialisierung ist ziemlich teuer. Um die Leistung zu verbessern, serialisiere ich den Benutzer nun einmal, wenn er sich anmeldet, und benutzt dann die serialisierte Version für JSON auf der Seite platzieren. Hier vor und nach, eine spürbare Verbesserung bei der Leistung:

// Vor

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);** 
} 

// Nach

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; 
} 

Das User-Objekt keine Setter-Methoden hat, gibt es keine Möglichkeit, die Daten würden sich immer ändern, wenn sich der Benutzer nicht abmeldet und dann wieder einbucht. In diesem Fall würde ich also sagen, dass es sicher ist, den Wert zwischenzuspeichern.

1

IMO, wenn Sie wirklich besorgt über die Leistung sind, ist dies ein bisschen übertrieben oder umfangreich, aber es gibt ein paar Möglichkeiten, um sicherzustellen, dass die Variable „Im Cache“ von VM,

Erstens können Sie erstellen endgültig Statische Variablen der Ergebnisse (wie in Ihrem Beispiel 1 oder 0), daher wird nur eine Kopie für die gesamte Klasse gespeichert, dann ist Ihre lokale Variable nur ein boolescher Wert (nur 1 Bit verwendend), behält aber immer den Ergebniswert von double (auch, vielleicht können Sie int verwenden, wenn es nur 0 oder 1) ist

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); 
} 

oder wenn Sie sind in der Lage, die Größe bei der Initialisierung der Klasse festlegen, die Sie gehen können, w ith diesem können Sie die letzte Variable durch Konstruktor festgelegt und statisch, aber da dies eine lokale Variable ist, können Sie mit dem Konstruktor gehen:

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

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

kann dies über foo.getSize()

2

Wenn der Wert zugegriffen werden Größe wurde jedes Mal berechnet durch Schleifen durch ein Array und damit nicht O (1), Caching der Wert hätte offensichtliche Vorteile der Leistung. Aber da Größe von Foo ist nicht zu erwarten, dass zu irgendeinem Zeitpunkt zu ändern, und es ist O (1), Caching der Wert hilft hauptsächlich in der Lesbarkeit. Ich empfehle, den Wert weiter zu cachen, weil die Lesbarkeit oft ein größeres Problem ist als die Leistung in modernen Computersystemen.

1

In meinem Code würde ich es zwischenspeichern, wenn entweder die Methode getSize() zeitaufwendig ist oder - und das ist häufiger - das Ergebnis in mehr oder weniger komplexen Ausdrücken verwendet wird.

Zum Beispiel, wenn ein Versatz von der Größe

int offset = fooSize * count1 + fooSize * count2; 

ist leichter zu lesen (für mich) als

int offset = foo.getSize() * count1 + foo.getSize() * count2;