Почему шаблон прокси такой медленный? - PullRequest
1 голос
/ 08 мая 2011

По крайней мере в java, шаблон прокси имеет много накладных расходов - я не помню точные цифры, но при переносе крошечных методов прокси занимает примерно в 50 раз больше, чем упакованный метод.Вот, например, почему java.awt.image.BufferedImage.setRGB & getRGB действительно действительно медленные;есть три прокси, которые обертывают действительные значения byte[].

Почему 50 раз ?!Почему прокси не удваивает время?


Edit: = (

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

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

Ответы [ 3 ]

7 голосов
/ 08 мая 2011

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

Попробуйте это:

Thingy.java

public class Thingy
{
    public int foo(int param1, int param2)
    {
        return param2 - param1;
    }
}

ThingyProxy.java:

public class ThingyProxy
{
    Thingy thingy;

    public ThingyProxy()
    {
        this.thingy = new Thingy();
    }

    public int foo(int param1, int param2)
    {
        return this.thingy.foo(param1, param2);
    }
}

WithoutProxy.java:

public class WithoutProxy
{
    public static final void main(String[] args)
    {
        Thingy t;
        int sum;
        int counter;
        int loops;

        sum = 0;
        t = new Thingy();
        for (loops = 0; loops < 300000000; ++loops) {
            sum = 0;
            for (counter = 0; counter < 100000000; ++counter) {
                sum += t.foo(1, 2);
            }
            if (sum != 100000000) {
                System.out.println("ERROR");
                return;
            }
        }
        System.exit(0);
    }
}

WithProxy.java:

public class WithProxy
{
    public static final void main(String[] args)
    {
        ThingyProxy t;
        int sum;
        int counter;
        int loops;

        sum = 0;
        t = new ThingyProxy();
        for (loops = 0; loops < 300000000; ++loops) {
            sum = 0;
            for (counter = 0; counter < 100000000; ++counter) {
                sum += t.foo(1, 2);
            }
            if (sum != 100000000) {
                System.out.println("ERROR");
                return;
            }
        }
        System.exit(0);
    }
}

Простые испытания на моей машине:

$ time java WithoutProxy 

real    0m0.894s
user    0m0.900s
sys     0m0.000s

$ time java WithProxy

real    0m0.934s
user    0m0.940s
sys     0m0.000s

$ time java WithoutProxy 

real    0m0.883s
user    0m0.850s
sys     0m0.040s

$ time java WithProxy

real    0m0.937s
user    0m0.920s
sys     0m0.030s

$ time java WithoutProxy 

real    0m0.898s
user    0m0.880s
sys     0m0.030s

$ time java WithProxy

real    0m0.936s
user    0m0.950s
sys     0m0.000s

Немного медленнее? Да. В 50 раз медленнее? Нет.

Теперь, синхронизация JVM общеизвестно трудна , и простые эксперименты, подобные вышеописанным, обязательно подозрительны. Но я думаю, что разница в 50 раз, вероятно, обнаружилась бы.

Редактировать : Я должен был упомянуть, что вышеприведенное с очень, очень небольшим числом циклов публикует номера, подобные этому:

real    0m0.058s
user    0m0.040s
sys     0m0.020s

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

4 голосов
/ 08 мая 2011

Когда код скомпилирован в собственный код, доступ к байтовому массиву будет выглядеть как инструкции цикла 3 1 (до тех пор, пока исходные и целевые данные будут горячими в кэше и не выровненные обращения к байту не будут оштрафованы. YMMV в зависимости от платформы).

Добавление вызова метода для хранения четырех байтов (в зависимости от платформы, но примерно так) добавит регистры проталкивания в стек, инструкцию вызова, инструкции доступа к массиву, инструкцию возврата и извлечение регистров из стека. Последовательность push / call / return / pop будет добавлена ​​для каждого слоя или прокси, и ни одна из этих инструкций в основном не выполняется за 1 цикл. Если компилятору не удастся встроить эти методы (что может случиться довольно легко), вы натолкнетесь на довольно серьезное наказание.

Прокси-серверы добавляют функциональность для преобразования глубины цвета и т. Д., Добавляя дополнительные издержки.

Кроме того, последовательный доступ к массиву может быть дополнительно оптимизирован компилятором (например, превращение операций сохранения в операции многобайтового доступа - до 8 бит за раз, при этом все еще занимая только 1 цикл), где прокси-вызовы усложняют это.

50x звучит немного высоко, но не слишком разумно, в зависимости от реального кода.

BufferedImage в частности добавляет много накладных расходов. Хотя сам по себе шаблон прокси-сервера может не добавлять заметных накладных расходов, использование BufferedImage, вероятно, делает. В частности, обратите внимание, что setRGB () синхронизирован, что может иметь серьезные последствия для производительности при определенных обстоятельствах.

2 голосов
/ 08 мая 2011

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


Тривиальные методы встроены, такие как геттеры и сеттеры. Они могут вообще не повлиять на производительность. Я очень сомневаюсь, что в 50 раз больше претендует на настоящую программу. Я ожидаю, что при правильном тестировании все будет ближе к тому, что нет разницы.

...