LINUX.ORG.RU

Сравнение производительности доступа к полям структур в Python, Common Lisp и С++

 , , , ,


0

2

Помните, я сказал, что постараюсь не писать до конца сентября? Так вот: у меня не получилось :) Кому не нравится тут тег Яр - заверяю, что он не для рекламы (и так все уже знают), а исключительно для удобства: мне по нему удобно искать, а вам его удобно заигнорить. Отношение к Яру здесь такое: я мучаюсь, как мне реализовать объекты Яра. Склоняюсь к тому, что сделать их с опцией «реализовать как defstruct/clos». Но дальше идут тонкости, в которые сейчас не лезу.

Итак, я нашёл тред пиписькометрии 7 - летней давности: https://www.linux.org.ru/forum/development/4535326, и там сравнивалась скорость каких-то циклов. но это неинтересно. Интересно сравнивать скорость доступа к полям объектов хотя бы. Я попробовал. Вот что в итоге вышло (платформа 64 разрядная).

С++

// cpp-benchmark.cpp
#include "stdio.h"
#include <cstdlib>

class test_class {
public:
	int fld1, fld2, fld3, fld4, fld5;
        test_class *next;
};


int inner(test_class *o,int bound) {
    int res=0;
    for (int i=0;i<bound;i++) {
        res += o->fld1;
        o->fld2 = o->fld1;
        o->fld3 = o->fld2;
        o->fld4 = o->fld3;
        o->fld5 = o->fld4;
        o->fld1 = res - 1;
        o = o->next;
        res = res % 16;

    }    
    return res;
}

int main(int argc, char* argv[])
{
    test_class o1;
    test_class o2;
    o1.fld1=1;
    o2.fld1=1;
    o1.next=&o2;
    o2.next=&o1;
    int n = 100*1000*1000;
    int result=inner(&o1,n);
    printf("%d %d\n",o1.fld5,result); // проверяем корректность и чтобы оптимизатор
    // не выкинул неиспользуемый код
    return 0;
}
// EOF
// запускаем:
g++ -O2 -o cpp-benchmark cpp-benchmark.cpp ; echo disassemble inner | gdb cpp-benchmark ; time ./cpp-benchmark 
// листинг пропускаю.

real	0m0.225s
user	0m0.216s
sys	0m0.004s

Лисп на структурах:

;; struct-benchmark.lisp
(in-package :cl-user)

(list
 '#.(restrict-compiler-policy 'safety)
 '#.(restrict-compiler-policy 'debug))
 
(declaim
 (optimize (speed 3) (safety 0) (debug 0)
           (space 0) (compilation-speed 0)))

(defparameter *times* (* 100 1000 1000))

(defstruct test-struct 
  (next nil :type (or null test-struct))
  (fld1 0 :type fixnum)
  (fld2 0 :type fixnum)
  (fld3 0 :type fixnum)
  (fld4 0 :type fixnum)
  (fld5 0 :type fixnum)
  )

(declaim (inline test-struct-fld1 test-struct-fld2 test-struct-fld3 test-struct-fld4 test-struct-fld5 (setf test-struct-fld1) (setf test-struct-fld2) (setf test-struct-fld3) (setf test-struct-fld4) (setf test-struct-fld5)
                    test-struct-next))

(defun inner (o n)
  (declare (type test-struct o))
  (declare (type fixnum n))
  (let ((res 0))
    (declare (type fixnum res))
    (dotimes (i n)
      (incf res (the fixnum (test-struct-fld1 o)))
      (setf (test-struct-fld2 o) (test-struct-fld1 o)
            (test-struct-fld3 o) (test-struct-fld2 o)
            (test-struct-fld4 o) (test-struct-fld3 o)
            (test-struct-fld5 o) (test-struct-fld4 o)
            (test-struct-fld1 o) (- res 1)
            o (test-struct-next o)
            res (mod res 16)))
    res))

(defun main ()
  (let* ((o1 (make-test-struct :fld1 1))
         (o2 (make-test-struct :fld1 1 :next o1))
         res)
    (setf (test-struct-next o1) o2)
    (setf res (inner o1 *times*))
    (format t "~S~%~S~%" (test-struct-fld5 o1) res)))

(let ((*trace-output* *standard-output*))
  (time (main)))
;;;; EOF
;; запускаем
>(load (compile-file "~/py/struct-benchmark.lisp"))
  0.394 seconds of real time
  0.436000 seconds of total run time (0.436000 user, 0.000000 system)
Лисп, но вместо inline ставим notinline - все аксессоры превращаются в полноценные функции. Получаем
real time 3.879 seconds
Лисп на CLOS:
;; clos-benchmark-with-types.lisp
(in-package :cl-user)

(list
 '#.(restrict-compiler-policy 'safety)
 '#.(restrict-compiler-policy 'debug))
 
(declaim
 (optimize (speed 3) (safety 0) (debug 0)
           (space 0) (compilation-speed 0)))

(defparameter *times* (* 100 1000 1000))

(defclass test-class-3 ()
  ((fld1 :initarg :fld1 :accessor test-class-3-fld1)
   (fld2 :accessor test-class-3-fld2)
   (fld3 :accessor test-class-3-fld3)
   (fld4 :accessor test-class-3-fld4)
   (fld5 :accessor test-class-3-fld5)
   (next :initarg :next :accessor test-class-3-next)))

(defun inner (o n)
  (declare (type fixnum n))
  (declare (type test-class-3 o))
  (let ((res 0))
    (declare (type fixnum res))
    (dotimes (i n)
      (incf res (the fixnum (test-class-3-fld1 o)))
      (setf (test-class-3-fld2 o) (the fixnum (test-class-3-fld1 o))
            (test-class-3-fld3 o) (the fixnum (test-class-3-fld2 o))
            (test-class-3-fld4 o) (the fixnum (test-class-3-fld3 o))
            (test-class-3-fld5 o) (the fixnum (test-class-3-fld4 o))
            (test-class-3-fld1 o) (the fixnum (- res 1))
            o (test-class-3-next o)
            res (mod res 16)))
    (print res)
    res))

(defun main()
  (let* (
         (o1 (make-instance 'test-class-3 :fld1 1))
         (o2 (make-instance 'test-class-3 :fld1 1 :next o1))
         res)
    (setf (test-class-3-next o1) o2)
    (setf res (inner o1 *times*))
    (format t "~S~%~S~%" (test-class-3-fld5 o1) res)))

(let ((*trace-output* *standard-output*))
  (time (main)))
#|
6.115секунд
|#

python:

# ~/py/oop-benchmark.py
import time

__times__ = 100*1000*1000

class TestClass3(object):
  
    def __init__(self):
        self.fld1 = 1
        self.fld2 = 0
        self.fld3 = 0
        self.fld4 = 0
        self.fld5 = 0
        self.next = self

def inner(o,count):
    res = 0
    for i in xrange(count):
        res += o.fld1
        o.fld2 = o.fld1
        o.fld3 = o.fld2
        o.fld4 = o.fld3
        o.fld5 = o.fld4
        o.fld1 = res - 1
        o = o.next;
        res = res % 16
    return res

def my_main():
    o1 = TestClass3()
    o2 = TestClass3()
    o1.next = o2
    o2.next = o1
    res = inner(o1,__times__)
    print '%s' % o1.fld5
    print '%s' % res

my_main()

# запуск:
#time python oop-benchmark.py 
#266666656
#3
#real	0m51.031s
#user	0m50.696s
#sys	0m0.052s
FreePascal
{oop-benchmark.fpc}
{$mode ObjFPC}
const n=100*1000*1000;
type
  PTest = ^TTest;
  TTest = object
  public
    fld1, fld2, fld3, fld4, fld5: Integer;
    next: PTest;
  end;

function inner(o: PTest; bound: Integer): Integer;
var i: Integer;
begin
  Result:=0;
  for i:=0 to bound-1 do with o^ do begin
    Result:=Result+fld1;
    fld2:=fld1;
    fld3:=fld2;
    fld4:=fld3;
    fld5:=fld4;
    fld1:=Result-1;
    o:=next;
    Result:=Result mod 16;
  end;
end;

var
  o1, o2: TTest;
  b: Integer;
begin
  o1.fld1:=1; o1.next:=@o2;
  o2.fld1:=1; o2.next:=@o1;
  b:=inner(@o1,n);
  WriteLn(o1.fld5,' ',b);
end.
{

fpc -O3 oop-benchmark.fpc
Free Pascal Compiler version 2.6.4+dfsg-4 [2014/10/14] for x86_64
Copyright (c) 1993-2014 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling oop-benchmark.fpc
Linking oop-benchmark
/usr/bin/ld.bfd: warning: link.res contains output sections; did you forget -T?
36 lines compiled, 0.1 sec 
1 warning(s) issued
den73@deb8:~/py$ time ./oop-benchmark
266666656 3

real	0m1.810s
user	0m1.776s
sys	0m0.008s
}

cython, pypy

##########
# test.pyx file

import cython

cdef class TestClass:

    cdef public unsigned int fld1, fld2, fld3, fld4, fld5
    cdef public object next
    
    def __cinit__(self):
        self.fld1 = 1
        self.fld2 = 0
        self.fld3 = 0
        self.fld4 = 0
        self.fld5 = 0
        self.next = self

@cython.overflowcheck(False)
@cython.cdivision(True)
cdef inner(TestClass o, count):
    cdef unsigned int res = 0, i
    for i in range(count):
        res += o.fld1
        o.fld2 = o.fld1
        o.fld3 = o.fld2
        o.fld4 = o.fld3
        o.fld5 = o.fld4
        o.fld1 = res - 1
        o = o.next;
        res = res % 16
    return res

def main():
    cdef TestClass o1, o2
    o1 = TestClass()
    o2 = TestClass()
    o1.next = o2
    o2.next = o1
    res = inner(o1, 100_000_000)
    print('%s' % o1.fld5)
    print('%s' % res)

##########
# setup.py file

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize

# to compile a module:
# python setup.py build_ext --inplace

extensions = [
    Extension('test', ['test.pyx'],
              extra_compile_args=['-O3'])
]

setup(name = 'access attrs benchmark',
      ext_modules = cythonize(extensions, annotate=True),
)

#########
# main.py file

from test import *
main()

# Запуск с помощью pip 
# 1. Создать ТРИ файла, исходник которых дан выше
# 2. В этой директории:
python setup.py build_ext --inplace
time python main.py

# Результаты:
real	0m0.306s
user	0m0.304s
sys	0m0.004s

$ time pypy oop-benchmark.py 
266666656
3

real	0m0,761s
user	0m0,736s
sys	0m0,020s

EMACS Lisp - несколько быстрее Питона.

★★★★★

lea -0x1(%rax),%r8d

вычисляем адрес левого операнда, и сохраняем в правом. если правильно помню этот синтаксис, команда эквивалентна r8d := rax - 1, только арифметические флаги не трогает

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

Спасибо за разъяснение про lea. Т.е. С++ внутри цикла только гоняет регистры туда-сюда, значит тест для С++ ничего не показывает для реального мира, где код более линейный. Это хорошо.

Выкинул теги С и Python.

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

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

В контексте проекта Яра хочу понять, что выбрать. С одной стороны, даже CLOS всё же в 8 раз быстрее Питона. Т.е. если цель - создать язык «Лучше Питона», то вполне можно брать CLOS и не париться. Если же хочется сделать реально быстрый, то нужно тянуться к структурам. Другое дело, что у структур нельзя, к примеру, протрассировать доступ к полю - доступ к полям инлайнится, а ставить хардверные точки прерывания тяжело. Есть ещё вариант самописных объектов на массивах - со всеми наворотами получается в 5 раз медленнее структур и в 4 раза быстрее CLOS (если я всё учёл). Т.е. есть не менее 3 вариантов с разными положениями на графике «фичи - скорость».

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

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

Iron_Bug ★★★★ ()

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

RedEyedMan4 ★★★★★ ()
Последнее исправление: RedEyedMan4 (всего исправлений: 1)

И наконец, С++

Так ведь классы в крестах это совсем не то. Для адекватного сравнения нужно не поля класса делать, а одно поле map<string, int> в классе и к нему аксессоры в духе slot-value. Вот такой «динамический» класс уже будет похож по возможностям на питон/лисп.

no-such-file ★★★★★ ()
Ответ на: комментарий от RedEyedMan4

Проблема в том, что если изначально у меня есть программа на С++ и я перевожу её на Python или CLOS, то она обрастает всей сложностью полноценного ООП. Нужна эта сложность или нет - платить за неё придётся. Я действительно могу усложнить пример на С++, но меня интересует разница в худшем случае, а не в лучшем.

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

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

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

если изначально у меня есть программа на С++ и я перевожу её на Python или CLOS, то она обрастает всей сложностью полноценного ООП.

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

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

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

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

удивительно, но попытки есть. проблема в том, что макаки не могут осилить С, а в сети полно библиотек на С, которые мало или вообще не поддерживаются. и вот они отважно пытаются переписать с С/С++ на всякие детские скрипты и регулярно обламываются. но это не учит их ничему, к сожалению.

Iron_Bug ★★★★ ()

С++ норовил закешировать в регистрах значения полей класса и записывать их только в конце цикла. Я пытался это побороть

То есть ты пытался побороть то, из-за чего C++ и выбирают? Зачем? В этом и есть его преимущество.

i-rinat ★★★★★ ()
Ответ на: комментарий от Iron_Bug

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

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

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

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

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

den73 ★★★★★ ()

Лол :-) Абсолютно бессмысленное сравнение :-) Нет никаких структур или полей, из которых ты что-то там читаешь и сравниваешь :-) Лол :-) Если лишь память, с которой работает процессор :-) То, как он с ней работает, не зависит от каких-то там ООП :-) Вся эта хренотень с ООП - лишь способ представления программы для лучшего понимания человеком :-) Поэтому не важно, как будут реализованы твои структуры или поддержка ООП :-) Важно лишь: а) насколько удобно с этим будет работать, б) насколько эффективным окажется компилятор/транслятор какой-либо ООП-лапши на каком-то человеко-понятном языке в машинный код :-) Все эти твои бенчмарки не стоят ровным счётом ничего :-) Лол :-)

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

Ах да :-) Цепепе уделал всех и в хвост и гриву не потому, что он круче остальных «с полями работает», и не потому, что его «ООП круче», а потому что компилятор в машкод реализован эффективней, чем у остальных :-) Поэтому сама попытка подавить оптимизацию эффективного компилятора, чтобы выяснить какую-то там «скорость работы с полями» и выглядит бессмысленно :-)

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

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

но вообще, есть же бенчмарки:
https://benchmarksgame.alioth.debian.org/

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

а при чём тут luajut? это никак не влияет на тормоза пистона. luajit из всех скриптов наиболее шустрый. хотя и не интерпретатор, в полном смысле. но на бенчмарки остальных языков это не влияет.

к тому же, весь код там приведён. можешь смотреть, насколько оптимально он написан.

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

luajit при том, что он там на многих бенчмарках обгонял Си. Т.е. это замечательная технология, которая должна была быть на этом сайте. А её там нет. Я думаю, не финансируется ли этот сайт Ораклом? Т.е. у меня нет веры в то, что автор данного сайта действительно хотел объективно сравнить языки между собой с целью выявления лидеров. А если мои подозрения верны, то у него есть миллион способов подвести меня к нужным ему выводам. Поэтому лучше писать бенчмарки вовсе с нуля, чем пользоваться этим сайтом.

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

luajit при том, что он там на многих бенчмарках обгонял С.

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

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

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

он просто идеологически отличается от многих других языков. в нём есть некоторая привлекательность. хотя практическое применение лиспа довольно ограничено.

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

Я лишь упомянул ее, так как самому интересно, сам я с ней не работал :) Так как эта штука не совсем jit, поэтому и результат предсказать трудно

Что касается ошибки надо смотреть документацию, компилятору возможно нужна определенная структура класса

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

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

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

Я думаю, не финансируется ли этот сайт Ораклом?

О Боже! Всё ещё хуже, чем я думал.

Поэтому лучше писать бенчмарки вовсе с нуля, чем пользоваться этим сайтом.

Ага. И в итоге появляются бенчи как в посте, которые абсолютно неверные.

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

Я не знаю, есть ли среди этих бенчей такой, к-рый бы отвечал именно за проверку скорости доступа к полям объекта/структуры. У тебя есть идеи на эту тему?

den73 ★★★★★ ()