2016-06-08 7 views
3

Введение:Лучшая практика именования в Perl для подпрограммы с выходными аргументами

Я обычно не используют выходные аргументы (аргументы, которые действуют в качестве входных и выходных значений или в качестве выхода только). Это для улучшения удобочитаемости и ремонтопригодности моего кода. Но опять же, на днях у меня внезапно возникла глубоко вложенная подпрограмма. И я попытался оптимизировать его, чтобы получить некоторые улучшения скорости.

Вот пример: у меня есть строка, представляющая строку обрезанного пробела, считанную из файла. Если строка пуста, я хотел бы заменить ее символом unicode, представляющим ключ возврата. Предположим, что я искал в Google и обнаружил, что символ (юникод U+21B5) выглядит хорошо [1]. Поэтому я написал короткую подпрограмму:

sub handle_empty_lines { 
    my ($str) = @_; 
    if ((!defined $str) || $str eq '') { 
     return "\x{21B5}"; 
    } 
    return $str; 
} 

и я использовал его, как это:

$line = handle_empty_lines($line); 

Теперь я хотел бы, чтобы оптимизировать этот вызов, но все-таки есть код быть читаемым и ремонтопригодны.

Первый вариант ставит его рядный:

$line = "\x{21B5}" if (!defined $str) || $str eq ''; 

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

Вот два других варианта,

  1. передать ссылку на $str, чтобы избежать копирования входной аргумент в подпрограмме (то есть: преобразовать вызов по значению для вызова по ссылке), или

  2. использовать встроенный вызов Perl посредством ссылки механизм.

Оба варианта вводит, "input/output argument" (то есть: аргумент, который действует как вход и выход) и, таким образом, снижает читаемость кода и делает обслуживание более трудным (на мой взгляд).

Позвольте третьему варианту сохранить исходную версию (вызов по значению). Ниже приведено краткое сравнение трех вариантов только скорости (не читаемость).

use strict; 
use warnings; 
use Benchmark qw(timethese); 

my $str1 = ''; 

timethese(
    2_000_000, 
    { 
     case1 => sub { my $test = $str1; case1(\$test) }, 
     case2 => sub { my $test = $str1; case2($test) }, 
     case3 => sub { my $test = $str1; $test = case3($test) }, 
    } 
); 

sub case1 { 
    if ((!defined $$_[0]) || $$_[0] eq '') { 
     $$_[0] = "\x{21B5}"; 
    } 
} 

sub case2 { 
    if ((!defined $_[0]) || $_[0] eq '') { 
     $_[0] = "\x{21B5}"; 
    } 
} 

sub case3 { 
    my ($str) = @_; 
    if ((!defined $str) || $str eq '') { 
     return "\x{21B5}"; 
    } 
    return $str; 
} 

Выход (Ubuntu ноутбук, Intel (R) ядра (ТМ) i7-4702MQ процессора @ 2.20GHz):

Benchmark: timing 2000000 iterations of case1, case2, case3... 
    case1: 1 wallclock secs (0.84 usr + 0.00 sys = 0.84 CPU) @ 2380952.38/s (n=2000000) 
    case2: 1 wallclock secs (0.45 usr + 0.00 sys = 0.45 CPU) @ 4444444.44/s (n=2000000) 
    case3: 1 wallclock secs (0.70 usr + 0.00 sys = 0.70 CPU) @ 2857142.86/s (n=2000000) 

Обратите внимание, что случай 2 составляет 87% быстрее, чем случай 1 и на 56% быстрее, чем случай 3.

Интересно, что вызов по ссылке (случай 1) медленнее, чем вызов по значению (случай 3).

Вопрос:

Предположим теперь я хотел бы держать случай 2:

sub handle_empty_lines { 
    if ((!defined $_[0]) || $_[0] eq '') { 
     $_[0] = "\x{21B5}"; 
    } 
} 

Тогда, если я называю это с помощью:

handle_empty_lines($line); 

это не дает ключ к разгадке читатель, что он изменяет $line.

Как мне с этим справиться? Я могу думать о двух вариантах:

  • Поместите комментарий после вызова:

    handle_empty_lines($line); # Note: modifies $line 
    
  • Изменить название подпрограммы. Дайте имя, которое дает указание на читателя, что $line модифицируется, например:

    handle_empty_lines__modifies_arg($line); 
    

Сноски:

1. Позже я узнал, что я мог бы использовать N{} escape, чтобы сделать код более читаемым, используя «\ N {СТРЕЛКА ВНИЗ С УГОЛОВНЫМИ ЛЕВОМЯМИ]} вместо« \ x {21B5} »

2. Для этого простого случая, я согласен, что это сомнительно, если это можно назвать любую форму беспорядком ..

3. 4444444,44/2380952,38 = 1,87

+0

Re "* Обратите внимание, что случай 2 составляет 87% 3 быстрее, чем случай 1, и на 56% быстрее, чем случай 3. *". Было бы намного легче заметить это, если бы вы использовали 'cmpthese' вместо' timethese' – ikegami

+0

@ikegami Да, я рассматривал использование 'cmpthese', но я обнаружил, что цифры меня смутили –

+0

Каждая из ваших функций отличается от других. Неопределенно отсутствует: 'sub case4 { if ((! Defined $ _ [0]) || $ _ [0] eq '') { return" \ x {21B5} "; } return $ _ [0]; } ' – ikegami

ответ

2

Я бы это написал

use utf8; 
use strict; 
use warnings 'all'; 
use feature 'say'; 
use open qw/ :std :encoding(utf-8) /; 

for ('', undef, 'xx') { 
    my $s = $_; 
    fix_nulls($s); 
    say $s; 
} 

sub fix_nulls { 
    for ($_[0]) { 
     $_ = "\x{21B5}" unless defined and length; 
    } 
} 
+0

Извините, я не совсем понимаю. Почему 'fix_nulls ($ s)' легче читать? –

+0

Я думаю, что это означает, что параметр будет изменен лучше, чем 'handle_empty_lines', что может делать почти что угодно, например игнорировать их – Borodin

1

Я всегда хотел разработать и пояснить ность. Я чувствую, что после того, как проблемы с производительностью начинают укусить дизайн интерфейса, пришло время перепроектировать. (Что для меня часто означает введение дополнительных слоев.) Я нахожу, что ваше окончательное обсуждение указывает, что нужно было бы заплатить совсем немного, так как все варианты несут проблемы. Я бы не перейти к @_.

Так что в этом случае я бы остаться ни с возвращением по значению

$line = handle_empty_lines($line); 

sub handle_empty_lines { 
    defined $_[0] && $_[0] ne '' && return $_[0]; 
    return "\x{21B5}"; 
} 

или по ссылке, которую я бы ожидать, чтобы быть немного быстрее.

handle_empty_lines(\$line); 

sub handle_empty_lines { 
    defined ${$_[0]} && ${$_[0]} ne '' && return 1; 
    ${$_[0]} = "\x{21B5}"; 
} 

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

Я бы выбрал, основываясь на том, что лучше подходит для дизайна.


Я добавил эти два с эталоном разместил ikegami как

sub micro { 
    defined $_[0] && $_[0] ne '' && return $_[0]; 
    return "\x{21B5}"; 
} 

sub microref { 
    defined ${$_[0]} && ${$_[0]} ne '' && return 1; 
    ${$_[0]} = "\x{21B5}"; 
} 

Полные результаты теста ikegami «s с этими двумя функциями добавил

 
       Rate with_ref microref baseline by_ref micro inplace 
with_ref 1522762/s  --  -13%  -19%  -34%  -36%  -43% 
microref 1742620/s  14%  --  -8%  -24%  -26%  -35% 
baseline 1890650/s  24%  8%  --  -18%  -20%  -29% 
by_ref 2296676/s  51%  32%  21%  --  -3%  -14% 
micro 2360880/s  55%  35%  25%  3%  --  -12% 
inplace 2680740/s  76%  54%  42%  17%  14%  -- 

Оказывается что «оптимизированная по mirco версия с хорошими ценами на доходность. На мой взгляд, эти результаты подразумевают, что для решения трудных проектных решений скорость не оправдана.


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

 
       Rate with_ref microref baseline by_ref micro inplace 
with_ref 1470954/s  --  -16%  -21%  -34%  -39%  -55% 
microref 1742620/s  18%  --  -6%  -21%  -27%  -47% 
baseline 1854974/s  26%  6%  --  -16%  -23%  -43% 
by_ref 2213644/s  50%  27%  19%  --  -8%  -32% 
micro 2399206/s  63%  38%  29%  8%  --  -27% 
inplace 3267412/s  122%  87%  76%  48%  36%  -- 

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

+0

. Вы говорите, что никогда не будете использовать случай 2 (даже если это приведет к значительному ускорению ваша программа), потому что в этом случае невозможно написать код поддерживаемого кода? (Таким образом, вы пожертвуете опытом пользователя в программе для комфорта сопровождающего) –

+1

@ HåkonHægland Well ...трудно сделать такое определенное утверждение. Но что такое _significant_ достаточно? Если простые вызовы функций вводят заметное замедление, я думаю, что нужно обязательно вернуться к чертежной доске. Это не просто удобство - это время разработки и ошибки. Они приходят через небольшие трещины в дизайне. Все дизайнерские решения с №2 неудобны и опасны. Третий, наименее, но включающий имя _how_, не является хорошим дизайном. Кроме того, я играл с ускорением простейших вызовов функций в GUI 'wxWidgets' - это сложно. Трудно оценить так точно. – zdim

+1

@ HåkonHægland Я добавил эти две функции к тесту [ikegami] (http://stackoverflow.com/users/589924/ikegami). Похоже, что это всего лишь% 14. Дайте мне знать, было бы неплохо добавить мои рассуждения выше на эту должность. – zdim

1

Ваши тесты изменяют несколько переменных. Вот лучший тест:

use strict; 
use warnings; 

use Benchmark qw(cmpthese); 

sub baseline { 
    my ($str) = @_; 
    if ((!defined $str) || $str eq '') { 
     return "\x{21B5}"; 
    } 
    return $str; 
} 


sub with_ref { 
    my ($ref) = @_; 
    if ((!defined $$ref) || $$ref eq '') { 
     return "\x{21B5}"; 
    } 
    return $$ref; 
} 


sub by_ref { 
    if ((!defined $_[0]) || $_[0] eq '') { 
     return "\x{21B5}"; 
    } 
    return $_[0]; 
} 


sub inplace { 
    if ((!defined $_[0]) || $_[0] eq '') { 
     $_[0] = "\x{21B5}"; 
    } 
} 

{ 
    my $str = ''; 

    cmpthese(-3, { 
     baseline => sub { my $test = $str; $test = baseline $test; }, 
     with_ref => sub { my $test = $str; $test = with_ref \$test; }, 
     by_ref => sub { my $test = $str; $test = by_ref $test; }, 
     inplace => sub { my $test = $str;   inplace $test; }, 
    }); 
} 

Результаты:

   Rate with_ref baseline by_ref inplace 
with_ref 1252657/s  --  -14%  -27%  -45% 
baseline 1461434/s  17%  --  -15%  -36% 
by_ref 1718499/s  37%  18%  --  -24% 
inplace 2271005/s  81%  55%  32%  -- 
  • Добавление ссылки замедляет.
  • Сохранение анонимности на 18% быстрее базовой линии, что составляет 0,14 микросекунды.

    $ perl -E'say 1/1718499 - 1/2271005' 
    1.41569475973388e-07 
    
  • в встроенном версия 55% быстрее, чем исходно, спасая 0,24 микросекунды.

    $ perl -E'say 1/1461434 - 1/2271005' 
    2.4392574799202e-07 
    

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

+0

Спасибо за добавленные тесты! Интересный, но все же 'inplace' намного быстрее. Это питти, которое нельзя использовать на практике только благодаря языковому дизайну. Я думаю, что дизайн языка должен позволять, например, $ str = #inplace ($ str) ', а затем компилятор просто изменил бы вызов на' inplace $ str'. Тогда и эффективность, и ремонтопригодность могут быть достигнуты .. :) –

+0

Он делает именно это для 'sort'. Сложнее делать в общем. – ikegami

+0

Я предполагаю, что это зависит от версии perl, у меня были похожие «сортировки», но совершенно разные результаты. Одна вещь, которую я заметил, заключается в том, что подсистемы не являются достаточно эквивалентными, так как в варианте «inplace» отсутствует оператор return. Для меня это составляло примерно 3-4% разницы в 5.16.2 (Mac OS X) и 13-15% -ную разницу в 5.18.1 (Linux в VM). – polettix

 Смежные вопросы

  • Нет связанных вопросов^_^