2014-01-27 3 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(); 'to' double fooSize = foo.getSize(); '. –

ответ

15

Несколько факторов (в произвольном порядке), что я считаю, при принятии решения о том, следует ли сохранить значение, возвращаемое вызовом «метод получения()»:

  1. Performance из get() - если API не указывает, или если вызывающий код тесно связан с вызываемым методом, нет никаких гарантий эффективности метода get(). Теперь код может быть хорошим в тестировании, но может ухудшиться, если методы get() будут меняться в будущем или если тестирование не отражает реальных условий. (например, тестирование только тысячи объектов в контейнере, когда в реальном мире контейнер может иметь десять миллионов) Используется в для цикла, метод Get() будет вызываться перед каждой итерации

  2. читаемости - A переменной может быть задано конкретное и описательное имя, предоставляющее разъяснение его использования и/или значения таким образом, который может быть не ясен из встроенных вызовов метода get(). Не стоит недооценивать ценность этого для тех, кто просматривает и поддерживает код.

  3. Thread safety - Может ли значение, возвращаемое методом get(), потенциально измениться, если другой поток изменяет объект, пока вызывающий метод выполняет свою работу? Должно ли такое изменение отразиться на поведении вызывающего метода?

Что касается вопроса о том или нет компиляторов будет кэшировать его сами, я собираюсь спекулировать и говорить, что в большинстве случаев ответ не должен быть «нет». Единственный способ, с помощью которого компилятор мог бы это сделать, - определить, сможет ли метод get() возвращать одно и то же значение при каждом вызове. И это может быть гарантировано только в том случае, если сам метод get() был помечен как final, и все, что он сделал, это вернуть константу (т.е. объект или примитив также отмечен как «final»). Я не уверен, но я думаю, что это, вероятно, не сценарий, с которым компилятор беспокоится. Компилятор JIT имеет больше информации и, следовательно, может иметь большую гибкость, но у вас нет гарантий, что какой-то метод получит JIT'ed.

В заключение не беспокойтесь о том, что может сделать компилятор. Кэширование возвращаемого значения метода get() - это, вероятно, правильная вещь в большинстве случаев и редко (i.e почти никогда не) будет неправильной вещью. Благодарите написанный код, который читается и исправляет код, быстрый (est) и кричащий.

+1

Обычно рекомендуется кэшировать стек. Рекурсивные вызовы будут потреблять больше памяти в стеке, и это может быть проблемой для очень конкретных случаев, но следует избегать глубокой рекурсии, чтобы избежать ошибок переполнения стека. – jbaliuka

5

Я не знаю, есть ли «правильный» ответ, но я бы сохранил местную копию.

В вашем примере, я вижу, что getSize() тривиально, но в реальном коде я не всегда знаю, тривиально это или нет; и даже если сегодня это тривиально, я не знаю, что кто-то не придет и не изменит метод getSize(), чтобы сделать его нетривиальным в будущем.

3

Самым большим фактором будет производительность. Если это простая операция, которая не требует много циклов процессора, я бы сказал, не кэшировать ее. Но если вам постоянно нужно выполнять дорогостоящую операцию над данными, которые не меняются, то обязательно их кешируйте. Например, в моем приложении пользователь, который в настоящий момент вошел в систему, сериализуется на каждой странице в формате 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; 
} 

Объект Пользователь не имеет методов сеттер, нет никакого способа, данные будут когда-либо изменяться, если пользователь не выберет и не вернется, так что в этом случае я бы сказал, что безопасно кэшировать значение.

1

ИМО, если вы действительно беспокоитесь о производительности, это немного излишним или обширный, но есть несколько способов, чтобы гарантировать, что переменная «в кэше» на ВМ,

Во-первых, вы можете создать окончательный статические переменные результатов (согласно вашему примеру 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); 
} 

или, если вы можете установить размер при инициализации класса вы можете пойти ш Ith этого, вы можете установить окончательную переменную через конструктор, и статические, но поскольку это локальная переменная, которую можно перейти с помощью конструктора:

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; 
Смежные вопросы