Какой из следующих фрагментов Java-кода лучше? - PullRequest
4 голосов
/ 22 мая 2010

Это не должно быть субъективно, я ищу причины, основанные на использовании ресурсов, производительности компилятора, производительности GC и т. Д., А не на элегантности.Да, и положение скобок не учитывается, поэтому никаких стилистических комментариев, пожалуйста.

Возьмите следующий цикл:

Integer total = new Integer(0);
Integer i;
for (String str : string_list)
{
    i = Integer.parse(str);
    total += i;
}

против ...

Integer total = 0;
for (String str : string_list)
{
    Integer i = Integer.parse(str);
    total += i;
}

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

Есть довольно много других случаев, когда я стремлюсь охватывать переменные более широко, чем, возможно, необходимо, поэтому я подумал, что попрошу здесь устранить пробел в моих знаниях.Также обратите внимание, что присвоение переменной при инициализации либо связано с новым оператором, либо нет.Есть ли какая-либо разница между этими видами полистилистической полуоптимизации?

Ответы [ 10 ]

7 голосов
/ 22 мая 2010

Второй - то, что я бы предпочел. Там нет функциональной разницы, кроме области видимости.

Установка одной и той же переменной в каждой итерации не имеет значения, поскольку Integer является неизменным классом. Теперь, если бы вы модифицировали объект вместо того, чтобы каждый раз создавать новый, тогда была бы разница.

И, как примечание, в этом коде вы должны использовать int и Integer.parseInt() вместо Integer и Integer.parse(). Вы вводите довольно много ненужного бокса и распаковки.


Редактировать: Прошло много времени с тех пор, как я перебирал байт-код, поэтому думал, что снова запачкаю руки.

Вот тестовый класс, который я скомпилировал:

class ScopeTest {
    public void outside(String[] args) {
        Integer total = 0; 
        Integer i; 
        for (String str : args) 
        { 
            i = Integer.valueOf(str); 
            total += i; 
        }
    }
    public void inside(String[] args) { 
        Integer total = 0; 
        for (String str : args) 
        { 
            Integer i = Integer.valueOf(str); 
            total += i; 
        }
    }
}

Вывод байт-кода (извлекается с помощью javap -c ScopeTest после компиляции):

Compiled from "ScopeTest.java"
class ScopeTest extends java.lang.Object{
ScopeTest();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public void outside(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   4:   astore_2
   5:   aload_1
   6:   astore  4
   8:   aload   4
   10:  arraylength
   11:  istore  5
   13:  iconst_0
   14:  istore  6
   16:  iload   6
   18:  iload   5
   20:  if_icmpge       55
   23:  aload   4
   25:  iload   6
   27:  aaload
   28:  astore  7
   30:  aload   7
   32:  invokestatic    #3; //Method java/lang/Integer.valueOf:(Ljava/lang/String;)Ljava/lang/Integer;
   35:  astore_3
   36:  aload_2
   37:  invokevirtual   #4; //Method java/lang/Integer.intValue:()I
   40:  aload_3
   41:  invokevirtual   #4; //Method java/lang/Integer.intValue:()I
   44:  iadd
   45:  invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   48:  astore_2
   49:  iinc    6, 1
   52:  goto    16
   55:  return

public void inside(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   4:   astore_2
   5:   aload_1
   6:   astore_3
   7:   aload_3
   8:   arraylength
   9:   istore  4
   11:  iconst_0
   12:  istore  5
   14:  iload   5
   16:  iload   4
   18:  if_icmpge       54
   21:  aload_3
   22:  iload   5
   24:  aaload
   25:  astore  6
   27:  aload   6
   29:  invokestatic    #3; //Method java/lang/Integer.valueOf:(Ljava/lang/String;)Ljava/lang/Integer;
   32:  astore  7
   34:  aload_2
   35:  invokevirtual   #4; //Method java/lang/Integer.intValue:()I
   38:  aload   7
   40:  invokevirtual   #4; //Method java/lang/Integer.intValue:()I
   43:  iadd
   44:  invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   47:  astore_2
   48:  iinc    5, 1
   51:  goto    14
   54:  return

}

Вопреки моим ожиданиям, между ними было одно различие: в outside() переменная i все еще занимала регистр, даже если она была исключена из фактического кода (обратите внимание, что все iload и istore инструкции указывают на один регистр выше).

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

(А что касается моего предыдущего примечания, вы можете видеть, что для добавления двух объектов Integer Java должна распаковать оба с intValue, добавить их, а затем создать новое Integer с valueOf. Не делать это без крайней необходимости, потому что это бессмысленно и медленнее.)

4 голосов
/ 22 мая 2010

Второй вариант намного лучше, потому что первый стиль должен использоваться только в коде C как обязательный.Java позволяет встроенным объявлениям минимизировать область видимости переменных, и вы должны воспользоваться этим.Но ваш код может быть улучшен:

int total = 0;
for (String str: stringList) {
    try {
        total += Integer.valueOf(str);
    } catch(NumberFormationException nfe) {
        // more code to deal with the error
    }
}

Это соответствует соглашению о стиле кода Java.Прочитайте полное руководство здесь: http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html

0 голосов
/ 22 мая 2010

Вопрос о том, какую область видимости переменной использовать, является проблемой читабельности больше всего на свете.Код лучше понимается, когда каждая переменная ограничена областью, где она фактически используется.

Теперь, если мы проверим технические последствия использования широких / узких областей, я считаю, что есть преимущество в производительности / занимаемой площади при узких областях.Рассмотрим следующий метод, где у нас есть 3 локальные переменные, принадлежащие одной глобальной области:

private static Random rnd = new Random();

public static void test() {
    int x = rnd.nextInt();
    System.out.println(x);

    int y = rnd.nextInt();
    System.out.println(y);

    int z = rnd.nextInt();
    System.out.println(z);      
}

Если вы разберете этот код (например, с помощью javap -c -verbose {имя класса}), вы увидитечто компилятор резервирует 3 слота для локальных переменных в структуре фрейма стека метода test ().

Теперь предположим, что мы добавили несколько искусственных областей:

public static void test() {
    {
        int x = rnd.nextInt();
        System.out.println(x);
    }

    {
        int y = rnd.nextInt();
        System.out.println(y);
    }

    {
        int z = rnd.nextInt();
        System.out.println(z);
    }
}

Если вы сейчас разберете код, вы заметите, что компилятор резервирует только 1 слот для локальных переменных.Поскольку области полностью независимы, каждый раз, когда используются x, y или z, используется один и тот же слот # 0.

Что это значит?

1) Узкие области сохраняют пространство стека

2) Если мы имеем дело с переменными объекта, это означает, что объекты могут стать недоступными быстрее,следовательно, имеют право на получение GC раньше, чем в противном случае.

Опять же, обратите внимание, что эти 2 «преимущества» действительно незначительны, и проблема читабельности должна быть безусловно самой важной.

0 голосов
/ 22 мая 2010

Если у вас есть положительные числа в вашем списке, и вы серьезно относитесь к

Я ищу причины, основанные на использование ресурсов, компилятор производительность, производительность GC и т. д. а не элегантность.

Вы должны реализовать это самостоятельно, например:

import java.util.ArrayList;
import java.util.List;

public class Int {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("10");
        list.add("20");
        int total = 0;
        for (String str : list) {
            int val = 0;
            for (char c : str.toCharArray()) {
                val = val * 10 + (int) c - 48;
            }
            total += val;
        }
        System.out.print(total);
    }
}

Единственная вещь, относящаяся к GC, будет toCharArray(), которую можно заменить другим циклом, используя charAt()

0 голосов
/ 22 мая 2010

Второй вариант предпочтительнее для удобочитаемости, удобства обслуживания и эффективности.

Все три из этих целей достигнуты, потому что вы кратко объясняете, что вы делаете и как используются ваши переменные. Вы объясняете это как разработчикам, так и компилятору. Когда переменная i определена в блоке for, все знают, что ее можно игнорировать за пределами блока и что значение допустимо только для этой итерации блока. Это приведет к тому, что сборщик мусора сможет легко пометить эту память как свободную.

Я бы предложил не использовать Integer для промежуточных значений. Суммируйте сумму как int и после цикла создайте объект или используйте автобокс.

0 голосов
/ 22 мая 2010

Второй, поскольку вы хотите сохранить область видимости ваших переменных как можно более «внутренней».Преимущество меньшего объема - меньше шансов на столкновение.В вашем примере всего несколько строк, поэтому преимущество может быть не столь очевидным.Но если оно больше, наличие переменных меньшего объема определенно более выгодно.Если позже кому-то еще придется взглянуть на код, ему нужно будет сканировать весь путь обратно до определения метода, чтобы узнать, что такое i .Аргумент не сильно отличается от того, почему мы хотим избежать глобальной переменной.

0 голосов
/ 22 мая 2010

Ну, в этом случае вы создаете примитив Integer каждый раз, когда говорите i = Integer.parseInt(str) (где i - Integer), поэтому (если Java не знает, как его оптимизировать), оба случая почти одинаково неэффективно. Попробуйте использовать int вместо:

int total = 0;
for (String str : string_list)
{
    int i = Integer.parseInt(str);
    total += i;
}

Теперь мы возвращаемся к вопросу о том, помещать объявление int внутри или снаружи. Если предположить, что компилятор Java имеет приличную оптимизацию, я бы сказал, что это не имеет значения. Помимо эффективности, считается хорошей практикой объявлять переменные как можно ближе к их использованию.

0 голосов
/ 22 мая 2010

Ни.Integer.valueOf(0); будет использовать ссылку на кэшированный 0.:)

0 голосов
/ 22 мая 2010

Второй гораздо лучше. Как можно более узкая область видимости переменных делает код более легким для чтения и для поддержки , что гораздо важнее в целом, чем различия в производительности между этими примерами, которые тривиальны и легко оптимизируются.

0 голосов
/ 22 мая 2010

Это не имеет существенного значения, кроме как на последней итерации, когда ссылка очищается быстрее во втором примере (и это было бы моим предпочтением - не столько по этой причине, но по ясности.)

Держите прицел на минимально возможном уровне. Виртуальная точка доступа выполняет анализ, чтобы определить, когда ссылки больше не доступны, и на основании этого распределяет некоторые объекты в стеке, а не в куче. Как можно меньший объем помогает этому процессу.

Я бы спросил, почему вы используете Integer вместо простого int ... или, может быть, просто в качестве примера?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...