LINUX.ORG.RU

кортежи не нужны?

 


0

1

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

struct gasket {
    float weight;
    unsigned height;
    unsigned diameter;
};
 
void main() {
    struct gasket obj = { 12.f, 120, 30 };

Можно вернуть структуру из функции, можно передать в функцию. Спрашивается, а зачем тогда кортежи? Что нового они вообще дают хоть в каком-то языке? Понятно, что в языках, где список может состоять из элементов только одного типа, и где нет типа any/t/variant, кортежи могут быть кривым костылём для полиморфных списков. Ну и теоретически они могут быть эффективнее. Но Си всё равно даёт то же самое, плюс поля кортежа ещё и именованные. Единственное неудобство - это то, что тип структуры нужно объявлять. Но в этом можно видеть плюс, т.к. объявление структуры заставляет дать вещам имена и тем самым сделать программу более явной и понятной. А типизация полей структуры убережёт от ошибок.

Единственное, что я заметил - так это то, что времена жизни, константность и прочие атрибуты объектов в кортеже никак не связаны, а в структуре - связаны. Но если структура наполнена указателями, то и это почти не имеет значения.

Что я упустил?

★★★★★

Последнее исправление: den73 (всего исправлений: 3)
Ответ на: комментарий от monk

по сути инициализации блока памяти на стеке, то есть пара ассемблерных инструкций

Если ты посмотришь в инспекторе на лямбду, то там есть «значения, которые она замыкает». Если такие значения есть, то нужно заполнить в структуре все поля ссылками на те места, откуда они выросли. Плюс переменные, к-рые она замыкает, в каких-то случаях должны иметь некий «box» (собственно место хранения переменной), к-рый должен быть выделен не в стеке, а в куче. Поскольку две лямбды, замыкающие одну и ту же переменную, сохраняют связь между собой на всю оставшуюся жизнь. Твоя лямбда замыкает accessors, значит, что-то из перечисленного к ней может относиться.

Кроме того, accessor-ы структур объявлены как inline. Если ты передаёшь их в функцию, то они становятся медленнее во много раз (полный вызов функции вместо косвенной адресации с константным смещением).

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

Струетура в с++ по сути и есть две переменные. При передаче в функцию по ссылке/указателю мы внутри функции обращаемся к первой и второй переменной по адресу и адресу+офсет. Структуры в си это ж просто данные. Мы выделили память размером раз1+раз2+выравнивание. Передаем в функцию указатель и работаем там с памятью по указателю.

Dudraug ★★★★★
()
Ответ на: комментарий от Dudraug

На с++

До кучи два моих варианта. В C++ используется перегрузка operator<, вот также на лиспе:

(defstruct person
  (name          "" :type string)
  (department-id 0  :type fixnum))

(defgeneric less (a b))

(defmethod less ((a real) (b real))
  (< a b))

(defmethod less ((a string) (b string))
  (string< a b))

(defmethod less ((a sequence) (b sequence))
  (eq (some (lambda (x y)
              (cond
                ((less x y) t)
                ((less y x) '#:false)))
            a b)
      t))

(defun make-test-persons ()
  (loop
    :repeat 10
    :collect (make-person :name (format nil "name~d" (random 10))
                          :department-id (random 3))))

(defun test-3.1 ()
  (sort (make-test-persons) #'less
        :key (lambda (person)
               (list (person-department-id person)
                     (person-name          person)))))

(defun test-3.2 ()
  (sort (make-test-persons) #'string< :key #'person-name))

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

(defmacro multiple-less ((a b) &body clauses)
  (let ((original-a (gensym "A"))
        (original-b (gensym "B")))
    (labels ((less (clauses)
               (destructuring-bind (form &optional (key #'identity)) (pop clauses)
                 `(let ((,a (funcall ,key ,original-a))
                        (,b (funcall ,key ,original-b)))
                    ,(if clauses
                       `(cond
                          (,form
                            t)
                          ((let ((,a ,b) (,b ,a))
                             ,form)
                           nil)
                          (t
                            ,(less clauses)))
                       form)))))
      (if clauses
        `(lambda (,original-a ,original-b)
           ,(less clauses))
        (constantly t)))))
                   
(defun test-3.1* ()
  (sort (make-test-persons)
        (multiple-less (a b)
          ((< a b)       #'person-department-id)
          ((string< a b) #'person-name))))

Я думаю, что если поколдовать с декларациями, этот код будет эквивалентен C++ в плане скорости.

Jini ★★
()
Последнее исправление: Jini (всего исправлений: 1)
Ответ на: комментарий от Jini

Этот код останется тем же если в структуре будет 10 полей и надо сделать сортировку по 8, а типы разные (string, int,float, mytype)?

А в другом месте проги надо сделать сортировку по только двум параметрам?

А если надо сделать сортировку по 4 полям (int, string, float, mytype), но при этом сортировку по строковому полю сделать без учета регистра?

-----

В любом случае... Я конечно понимаю, что все эти шаблоны с переменым числом аргументов - это все пришло из функциональных языков и используют приемы из языков вроде лиспа. Но вопрос не в этом. Вопрос в том насколько нужны кортежи(я так и не понял, они же есть в лиспе?) и насколько запутанным и неэффективным будет код без них. Я НЕ пытаюсь тут устроить срач с++ vs лисп, ибо понимаю, что туплы - это функциональщина. Вопрос лишь в нужности кортежей автору в новом языке не более. Если без них код получается как простыня, да еще и с оверхэдом, а без них компактно и «быстро», то надо полагать, что они таки нужны.

Dudraug ★★★★★
()
Последнее исправление: Dudraug (всего исправлений: 1)
Ответ на: комментарий от Dudraug

Этот код останется тем же если в структуре будет 10 полей и надо сделать сортировку по 8, а типы разные (string, int,float, mytype)?

Да. В этом вопросе С++ не догонит лисп, скорее всего, никогда. clang пытается что-то такое, до этого был opencxx. Но всё это слишком сложно для реального использования.

Только Jini использовал defmethod, к-рый довольно медленный. Чтобы сделать быстро нужно делать совсем по-другому, я даже слёту не скажу, как. Я, правда, со вчера успел забыть, в тюплах типы в С++ статически задаются? Если да, то для статического выбора метода сравнения для поля по заданным статически типам полей «тюпла» нужно будет, как минимум, чтобы выводильщик типов знал об этих статических типах. Если же типы динамические, то всё более-менее ок, хотя возможно defmethod нужно заменить на что-то более хитрое.

Кроме того, использована ссылка на функцию доступа к полю структуры и funсall от неё, я уже вчера monk-у указал на то, что скорее всего это будет вызов ф-ии по указателю, а не inline подстановка, соответственно замедление во много раз. Если я неправ и SBCL достаточно умён, чтобы сделать тут inline, то и хорошо. Если я прав, то это легко переделать. Кстати, надо бы это проверить, вопрос интересный.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Вот сцуко :) Я был неправ - он это способен оптимизировать и синлайнить

(funcall #'some-inline-function its args)

И на monk вчера зря наехал я про это. Так что пока проблема только в defmethod (если я опять не окажусь ламером).

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Проверочный код такой:


(in-package :cl-user)

(declaim (optimize (safety 0) (speed 3) (debug 0) (space 0) (compilation-speed 0)))
(proclaim '(optimize (safety 0) (speed 3) (debug 0) (space 0) (compilation-speed 0)))

(defstruct person id name)

(defun funcall-person-id (person)
  (funcall #'person-id (the person person)))

(defun funcall-person-id-2 (person)
  (person-id (the person person)))
Компилируем, загружаем и далее disassemble funcall-pseron-id. Видим:
; disassembly for FUNCALL-PERSON-ID
; Size: 17 bytes. Origin: #x10081F9531
; 31:       84042500000F20   TEST AL, [#x200F0000]            ; safepoint
                                                              ; no-arg-parsing entry point
; 38:       488B510D         MOV RDX, [RCX+13]
; 3C:       488BE5           MOV RSP, RBP
; 3F:       F8               CLC
; 40:       5D               POP RBP
; 41:       C3               RET

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

В этом вопросе С++ не догонит лисп, скорее всего, никогда

В смысле, я привел код который это ДЕЛАЕТ. А ты говоришь, что не догонет никогда. wtf?

Dudraug ★★★★★
()
Ответ на: комментарий от den73

Я, правда, со вчера успел забыть, в тюплах типы в С++ статически задаются?

Да, само собой.

Dudraug ★★★★★
()
Ответ на: комментарий от Dudraug

Вопрос в том насколько нужны кортежи(я так и не понял, они же есть в лиспе?) и насколько запутанным и неэффективным будет код без них.

Запутанным вряд ли будет, как уже я писал, их всегда можно спокойно заменить вектором из универсального типа. Методы сравнения для них можно дописать, тут никакой проблемы нет. Если их не дописать, их может дописать пользователь. Неэффективным, да, может быть. Но у меня нет цели на данный момент догнать и перегнать С++ - эта цель выглядит недостижимой, нечего и браться без миллионных бюджетов на улучшение компилятора SBCL. А может быть и вообще из-за тегов типа и сборки мусора догнать С++ невозможно.

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

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

den73 ★★★★★
() автор топика
Ответ на: комментарий от Dudraug

Ты привёл некий частный случай. В целом лисп может в макросах то, что С++ не может в сумме макросов и шаблонов. Например, можно и легко проверять во время компиляции параметры SQL запроса. Oracle имеет для этого препроцессор, обрабатывающий код на Си (забыл название). В качестве примера посмотри на Qt. Им пришлось писать препроцессоры. В лиспе это было бы сделано макросами с трудозатратами в 100 раз меньше и гибкостью полученного решения в 10 раз больше.

Но это я не в рамках разжигания холивара. Рад твоему последовательному стремлению не втягиваться в него, просто делюсь информацией. Для холивара смысла нет, т.к разница в популярности очевидна и она не в пользу лиспа :)

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 2)
Ответ на: комментарий от Dudraug

Этот код останется тем же если в структуре будет 10 полей и надо сделать сортировку по 8, а типы разные (string, int,float, mytype)?

Да. В первом случае поля перечислены в списке в лямбде, во втором --- в параметрах макроса multiple-less.

А в другом месте проги надо сделать сортировку по только двум параметрам?

Да, только, как и в C++, надо как-то указать по каким параметрам идёт сортировка. В первом варианте более многословно, так как в лиспе нет оператора точка.

А если надо сделать сортировку по 4 полям (int, string, float, mytype), но при этом сортировку по строковому полю сделать без учета регистра?

В первом случае, как и в C++, надо вставить аналог toupper в лямбду, во втором можно сразу использовать string-lessp, которая сравнивает строки без учёта регистра.

Я тоже не собираюсь разжигать флейм C++ vs Common Lisp, просто люблю решать подобные задачки и решил закинуть свой ответ. Может, кому-то будет интересно.

я так и не понял, они же есть в лиспе?

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

(defun f ()
   ; возвращаем три значения
  (values 1 "два" 'три))

; принимаем три значения
(multiple-value-bind (one two three) (f)
   ; переменная one содержит 1, two --- "два", three --- 'три
   ...)

; много возвращаемых значений можно собрать в список:
(multiple-value-list (f)) ; вернёт список (1 "два" 'три)

; можно использовать только одно из значений, если нас другие не интересуют. Первое используется по умолчанию
(+ (f) 1) ; вернёт 2
(print (nth-value 1 (f))) ; напечатает "два"

Это и полиморфные контейнеры (списки, вектора) перекрывают, я думаю, то, для чего обычно используются кортежи в других языках программирования. Про это же писал den73 тут. Списки, кстати, можно разворачивать в переменные также как multiple-value-bind:

(defun g ()
  (list 1 2 3))

(destructuring-bind (a b c) (g)
   ; a имеет значение 1, b --- 2, c --- 3
   ...)
Аналогичного стандартного макроса для массивов нет, но он пишется на коленке за несколько минут.

Jini ★★
()
Ответ на: комментарий от den73

А ты не думай, а поколдуй. Вангую: не будет эквивалентен С++

Так что пока проблема только в defmethod

Во втором варианте нет defmethod, и у компилятора есть вся информация для инлайна. Попробую поколдовать вечером, только на слове меня не лови, пожалуйста :)

Jini ★★
()
Ответ на: комментарий от Jini

Во втором варианте нет defmethod

Да, сорри, не заметил :)

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

В общем расшираю мой вопрос тогда

Есть структура {int a; float b; mtype1 c; mtype2 d; string e;}

Как сделать сравнение структур

1) по a, c,e используя стандартные компараторы.

2) по c, e используя кастомный (нестандартный итератор только для поля c)

3) по с, е использую нестандартные компораторы (для e пусть будет апер кейс)

4) по c, d, e для c и e стандартный, для d свой.

И что б БЕЗ портянки и БЕЗ конвертирования в другой промежуточный тип, для которого определены операции сравнения.

Сформулирую задачук более коротко. Что делать когда тебе надо сделать сортировку по нескольким полям РАЗНЫХ типов. И в одних случаях надо юзать стандартные компораторы, а в других кастомные, но не для всех типов, а еще в одних случаях для других переменных применить кастомные.

Как на с++ решить такое без конвертирования в промежуточный тип и без портянок я не знаю. Не очень даже догадываюсь как это в принципе на псевдокоде решить даже. Единственный вариант - передавать еще список функций компараторов который будет по очереди юзаться. Но это на дичь похоже=)

Dudraug ★★★★★
()
Ответ на: комментарий от Dudraug

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

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

Но в целом лисп динамически типизированный (обычно) и диспатч стоит дорого. То, что мы тут рисуем - это «спорт высших достижений», когда применяется вывод типов и т.п. Идеоматичный код не такой и не столь быстрый. Поэтому идея иметь «универсальный оператор < для любых типов» в лиспе плохо работала бы - добавлялся бы ещё один динамический диспатч.

Кроме того, вообще не факт, что перегрузка операций - это хорошо.

Например, хорошо ли иметь расширяемый плюс? Я бы предпочёл иметь сужаемый плюс, например, чтобы можно было _запретить_ сложить double и single. Такое сложение в расчётных программах может иметь тяжёлые последствия. С++ позволяет расширять и делать помойку, а чистить помойку, сужая, С++ не позволяет (или пиши свой Plus словами).

Это, кстати, одна из «innovations», к-рую я пытаюсь позаимствовать из Лиспа в Яр.

den73 ★★★★★
() автор топика
Ответ на: комментарий от Jini

В первом случае, как и в C++, надо вставить аналог toupper в лямбду, во втором можно сразу использовать string-lessp, которая сравнивает строки без учёта регистра.

Ну так это тоже самое что

return std::make_tuple(to_upper(f.name), f.department_id, f.additional_data) < std::make_tuple(to_upper(s.name), s.department_id, s.additional_data);

...

return std::make_tuple(to_upper(f.name), f.department_id) < std::make_tuple(to_upper(s.name), s.department_id);
...

return std::tie(f.name, f.department_id) < std::make_tie(s.name, s.department_id);

...
return std::tie(f.name) < std::make_tie(s.name);

?

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

Dudraug ★★★★★
()
Последнее исправление: Dudraug (всего исправлений: 1)
Ответ на: комментарий от den73

Одна строчка «deriving (Eq, Ord)» - это «спорт высших достижений»?! Ни фига себе!

Ладно-ладно. Я так, зашел мельком просто

dave ★★★★★
()
Ответ на: комментарий от den73

Для холивара смысла нет, т.к разница в популярности очевидна и она не в пользу лиспа :)

У нас вообще разговор о кортежах. А не о том, что можно сделать в с++ и лиспе. Очевидно, что моя описанная задача решается и в с++ и в лиспе. Она даже скорее всего решается в с++ без котрежей(туплов), вопрос тут лишь в лаконичности/эффективности. Туплы в данном случае избавляют нас из портянки и без оверхэда (как было бы с массивами, рефлексией и всем таким похожим).

Dudraug ★★★★★
()
Ответ на: комментарий от Dudraug

И тут вопрос в уровне абстракций. Тупл в с++ это тоже по сути структура тип которой статично известен в компайл тайме. Вопрос лишь в уровне абстракций. тупл в с++ по сути шаблонная структура/класс наделенная некой семантикой и воплощающий некую абстрактуную идею. Но это позволяет нам не писать портянки=)

Dudraug ★★★★★
()
Ответ на: комментарий от Dudraug

В общем расшираю мой вопрос тогда

Немного подправил макрос, чтобы меньше текста писать:

(defmacro multiple-less ((&optional (a (gensym "A"))
                                    (b (gensym "B")))
                         &body clauses)
  (let ((original-a (gensym "A"))
        (original-b (gensym "B")))
    (labels ((less (clauses)
               (destructuring-bind (form &optional (key #'identity)) (pop clauses)
                 (when (atom form) (setf form `(,form ,a ,b)))
                 `(let ((,a (funcall ,key ,original-a))
                        (,b (funcall ,key ,original-b)))
                    ,(if clauses
                       `(cond
                          (,form
                            t)
                          ((let ((,a ,b) (,b ,a))
                             ,form)
                           nil)
                          (t
                            ,(less clauses)))
                       form)))))
      (if clauses
        `(lambda (,original-a ,original-b)
           ,(less clauses))
        (constantly t)))))

Есть структура {int a; float b; mtype1 c; mtype2 d; string e;}

(defclass mytype1 () ())
(defclass mytype2 () ())

(defstruct s
  (a 0                        :type integer)
  (b 0.0                      :type float)
  (c (make-instance 'mytype1) :type mytype1)
  (d (make-instance 'mytype2) :type mytype2)
  (e ""                       :type string))

; 1) по a, c,e используя стандартные компараторы. 
(multiple-less ()
  (<       #'s-a)
  (less    #'s-c)
  (string< #'s-e))

; 2) по c, e используя кастомный (нестандартный итератор только для поля c)
(multiple-less (a b)
  ((compare-somehow a b) #'s-c) ; вместо (compare-somehow a b) --- произвольный код
  (string<               #'s-e))

; 3) по с, е использую нестандартные компораторы (для e пусть будет апер кейс)
(multiple-less ()
  (compare-somehow2 #'s-с) ; если уже есть определённая ранее функция compare-somehow
  (string<          (lambda (s) (string-upcase (s-e s)))))

; 4) по c, d, e для c и e стандартный, для d свой. 
(multiple-less (a b)
  (less #'s-c)
  ((or (< (key1 a) (key2 b)) ; ну например, произвольный код
       (mytype2< a b))
   #'s-d)
  (string< #'s-e))

Код не проверял, так как лень тесты придумывать. Если выкатишь вариант на каком-нибудь языке, сделаю.

Jini ★★
()
Последнее исправление: Jini (всего исправлений: 1)
Ответ на: комментарий от Dudraug

Давай я так попробую: в С++ кортеж - это гибрид анонимной структуры и массива. От структуры - типы, от массива - доступ к элементам по порядку. Похоже на правду?

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

Да чего придумывать. В с++ кортеж - это кортеж, он такой каким он и должен быть. Реализован он через шаблонный класс с набором стандартных функций. Чего там еще придумывать?

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

Если тебя интересует реализация, то тупл - это шаблонная структура(да посмотри сам сорцы stl). Но по своему назначению, применению и области применения - это самый типичный кортеж. Я приводил примеры на с++, ибо мне проще приводить примеры на нем, не думаю, что примеры на других языках где есть кортежи будут конецептуально отличаться. Да и приемущества остануться теми же.

Dudraug ★★★★★
()
Ответ на: комментарий от Dudraug

Ну ладно, в целом всё ясно, ставлю галочку. Всем спасибо за участие. Надеюсь, что Jini поколдует над оптимизацией.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Я думаю, что если поколдовать с декларациями, этот код будет эквивалентен C++ в плане скорости.

А ты не думай, а поколдуй. Вангую: не будет эквивалентен С++

Поколдовал. Сначала расставил декларации в коде, который я приводил выше, и получил, что CL в 20 раз медленнее C++. Потом сообразил, что в CL UTF-8-строки, а в C++ std::string --- обычные байты. Переделал CL на сравнение байтовых массивов, получил, что CL в 3 раза медленнее C++. На мой взгляд, многовато, но моих знаний ассемблера не хватает понять в чём дело. Ещё интересно, что на CL функциональный вариант оказался в два раза быстрее итеративного --- я ожидал, что будет также или медленнее.

(defstruct s
  (x 0   :type fixnum)
  (y #() :type (vector (unsigned-byte 8))))

(defmacro multiple-less ((&optional (a (gensym "A"))
                                    (b (gensym "B")))
                        &body clauses)
  (let ((original-a (gensym "A"))
        (original-b (gensym "B")))
    (labels ((less (clauses)
               (destructuring-bind (form &optional (key #'identity)) (pop clauses)
                 (when (atom form) (setf form `(,form ,a ,b)))
                 `(let ((,a (funcall ,key ,original-a))
                        (,b (funcall ,key ,original-b)))
                    ,(if clauses
                       `(cond
                          (,form
                            t)
                          ((let ((,a ,b) (,b ,a)) ,form)
                           nil)
                          (t
                           ,(less clauses)))
                       form)))))
      `(lambda (,a ,b)
         ,(when (eq (caar clauses) 'declare)
            (pop clauses))
         (let ((,original-a ,a)
               (,original-b ,b))
           ,(less clauses))))))

(defun string->bytes (string)
  (map '(vector (unsigned-byte 8)) #'char-code string))

(setf a (make-s :x 1 :y (string->bytes "abc"))
      b (make-s :x 1 :y (string->bytes "abd")))

;; 35.8 seconds
;(setf f (multiple-less (a b)
;          (declare (optimize (speed 3) (safety 0))
;                   (type s a b))
;          (< #'s-x)
;          ((let ((la (length a))
;                 (lb (length b)))
;             (dotimes (i (min la lb) (< la lb))
;               (declare (type fixnum i))
;               (let ((x (aref a i))
;                     (y (aref b i)))
;                 (declare (type (unsigned-byte 8) x y))
;                 (cond
;                   ((< x y) (return t))
;                   ((> x y) (return nil))))))
;           #'s-y)))

;; 41.1 seconds
;(setf f (multiple-less (a b)
;          (declare (optimize (speed 3) (safety 0))
;                   (type s a b))
;          (< #'s-x)
;          ((loop
;             for x across a
;             for y across b
;             thereis (< x y)
;             never   (> x y)
;             finally (return (< (length a) (length b))))
;           #'s-y)))

; 17.5 seconds
(setf f (multiple-less (a b)
          (declare (optimize (speed 3) (safety 0))
                   (type s a b))
          (< #'s-x)
          ((eq (some (lambda (x y)
                       (declare (type (unsigned-byte 8) x y))
                       (cond
                         ((< x y) t)
                         ((< y x) '#:false)
                         (t       nil)))
                     a b)
               t)
           #'s-y)))

(time (dotimes (i 1000000000)
        (funcall f a b)))

#include <functional>
#include <tuple>
#include <iostream>
#include <string>

struct s {
  int         x;
  std::string y;
};

void test(
    const s& a,
    const s& b,
    const std::function<bool (const s&, const s&)>& cmp,
    unsigned long n
) {
  for (unsigned long i = 0; i < n; ++i) cmp(a, b);
};

int main(int argc, char** argv) {
  if (argc < 6) {
    std::cout << "Usage: " << argv[0] << " n a.x a.y b.x b.y\n";
    return 1;
  };

  unsigned long n = std::stoul(argv[1]);
  s a { std::stoi(argv[2]), argv[3] };
  s b { std::stoi(argv[4]), argv[5] };

  auto cmp = [](const s& a, const s& b) -> bool {
    return std::tie(a.x, a.y) < std::tie(b.x, b.y);
  };

  test(a, b, cmp, n);
  return 0;
};

C++ компилировался и запускался так:

g++ -std=gnu++17 -O3 test.cpp -o test && time ./test 1000000000 1 abc 1 abd

real    0m6.339s
user    0m6.338s
sys     0m0.000s

Jini ★★
()
Ответ на: комментарий от Jini
[tmp] > sbcl --version
SBCL 1.3.17
[tmp] > g++ --version
g++ (Gentoo 5.4.0-r3 p1.3, pie-0.6.5) 5.4.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Jini ★★
()
Ответ на: комментарий от Jini

Да, вот такова сермяжная правда. В gcc оптимизатор умнее + теги типа + (структура - это набор указателей, а в Сях данные могут лежать друг за дружкой). Поэтому 1/3 - как раз среднее по computer benchmark game.

den73 ★★★★★
() автор топика
Ответ на: комментарий от Jini

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

Dudraug ★★★★★
()
Ответ на: комментарий от Jini

Вариантов может быть несколько:

1) gprof или что-то вроде этого http://www.thegeekstuff.com/2012/08/gprof-tutorial/

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

3) Через valgrind. Цифры что он выдаст не факт что будут достоверными, но для оценки и сравнения могут и сгодиться.

Dudraug ★★★★★
()
Ответ на: комментарий от Dudraug

Задача профилирования --- выяснить где тормозит, а не насколько тормозит. Для измерения насколько тормозит достаточно time. Вся эта фигня с реалтаймовым приоритетом увеличит точность моих измерений на пару десятых процента, но это имеет смысл только в рамках серьёзного научного исследования. Здесь погрешность в 25% меня вполне устраивает. Вообще, исходное утверждение было

Я думаю, что если поколдовать с декларациями, этот код будет эквивалентен C++ в плане скорости.

Это утверждение и было проверено. Вывод: не эквивалентен, примерно в три раза медленнее.

Jini ★★
()
Ответ на: комментарий от Jini

Задача профилирования --- выяснить где тормозит, а не насколько тормозит.

Ты ж понимаешь, что измерение по time (тем более целого приложения) имеет ряд недостатков. И погрешнасть там может быть даже не 25% а сотни процентов.

Для твоих задач я таки был советовал валгринд.

Dudraug ★★★★★
()
Ответ на: комментарий от Dudraug

В данном измерении не будет погрешности в сотни процентов из-за запуска всего приложения. Это легко проверяется линейностью зависимости времени исполнения от размерности задачи, в чём я убедился перед публикацией. Прикинь сам: время от ввода команды до входа в цикл в варианте C++ составляет миллисекунды, а всё время выполнения --- шесть секунд. Какой максимально возможный вклад в погрешность?

Jini ★★
()
Ответ на: комментарий от Jini

Плюсану. Я думал, ты (Dudraug) говоришь о таких эффектах, как кеш процессора. Из-за него получается, что если в тесте мало кода, то код весь норовит поместиться в кеше процессора, и возникает иллюзия, что нужно везде делать тотальный inline. Если же кода много (как в реальной жизни), то может оказаться, что задержка при обращении к основной памяти превосходит выигрыш от inline.

Ну это только из того, что я знаю.

А если речь идёт о загрузке процессора, ну можно посмотреть, чем вообще компьютер занят. Если тест достаточно долгий (обычно десятков секунд хватает), то результаты оказываются достаточно одинаковыми для разных запусков, чтобы верить в точность 25%).

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

Кстати, кто мне объяснит, зачем используется RBP, скажу спасибо.

Быстрый и удобный способ выделить стековый фрейм. Возврат из функции с произвольным числом локальных переменных элементарен, даже после вещей типа alloca(3).

evilface ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.