vak: (Default)
[personal profile] vak
Провёл простейшее измерение memory footprint для примера "Hello world" на четырёх языках: C, Go, C++ и D. Размер требуемой памяти и количество прерываний подкачки страниц измерялись утилитой /usr/bin/time. Операционная система Ubuntu Linux.
Language    Resident Size   Page faults
----------------------------------------
C           1324k           61
Go          1064k           120
C++         3052k           126
D           2796k           144 
Как можно видеть, Go требует наименьшего размера памяти: в три раза меньше чем C++ и D, и на 30% меньше чем Си.

Date: 2017-02-26 04:32 (UTC)
brmail: (Default)
From: [personal profile] brmail
зашел в больницу, померял там температуру. к сожалению попал в морг, и теперь оказывается в больнице температура 22 градуса. Какой странный бенчмарк получился, не находите?

Date: 2017-02-26 04:42 (UTC)
proxfessor: (Default)
From: [personal profile] proxfessor
Resident size включает в себя хип и стек, а ни тот, ни другой "Hello world" особо и не нужны. Если бы убедить компилятор почти не выделять под них память, то еще неизвестно, каким был бы результат.

Date: 2017-02-26 08:36 (UTC)
dadv: (Default)
From: [personal profile] dadv
Стоит отметить, что это измеряет не "размер памяти, требуемой языку", а аппетиты run-time конкретной реализации и конкретной версии компилятора/run-time в конкретном дистрибутиве. В зависимости от этого результат может быть кардинально иным.

Date: 2017-02-26 09:40 (UTC)
dadv: (Default)
From: [personal profile] dadv
Тут дело даже не столько в версии GCC. У меня например на FreeBSD11 есть одновременно и gcc, и clang и они оба при компиляции/линковке используют один системный crt1, который вставляет ссылки на символы типа _init_tls, которые потом могут тянуть за собой половину libc по зависимостям.

Date: 2017-02-26 15:30 (UTC)
dadv: (Default)
From: [personal profile] dadv
А вот возьмем такой пример:

#include "hello.h"

#define MESG    "Hello, world!\n"
#define MESG_SZ (sizeof(MESG)-1)

int main() {
  write(1, MESG, MESG_SZ);
  _exit(0);
  return 0;
}


И соответствующий hello.h:

#define _exit(a)        __sys_exit(a)
#define write(a, b, c)  _write(a, b, c)

extern void __sys_exit(int);
extern int _write(int, void*, unsigned long);


И соберем его командой:

clang -ansi -pedantic -Wall -fomit-frame-pointer -Os -static -s -nostartfiles -nodefaultlibs -nostdlib -Wl,--entry=main,--gc-section -o hello hello.c -lc

Получаем статический бинарник hello размером в 1632 байт:

hello: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), statically linked, stripped

Из этих 1632 байт около 400 байт это засунутые внутрь строчки такого вида:

FreeBSD clang version 3.9.1 (tags/RELEASE_391/final 289601) (based on LLVM 3.9.1)
$FreeBSD: stable/11/lib/libc/sys/__error.c 288003 2015-09-20 03:49:08Z rodrigc $
$FreeBSD: stable/11/lib/libc/gen/errno.c 104704 2002-10-09 08:04:24Z peter $
$FreeBSD: stable/11/lib/libc/amd64/sys/cerror.S 296474 2016-03-08 00:09:34Z emaste $
.text
.shstrtab
.rodata
.eh_frame_hdr
.eh_frame
.data
.comment
.bss
Можно догадаться, что памяти и page faults оно будет использовать чуть более нуля.

При этом программа - чистый код на C, компилируется без единого предупреждения. И хотя цена достижения - потеря переносимости, у таких на первый взгляд "бесполезных извращений" есть своя практическая область применимости: http://dadv.dreamwidth.org/95981.html
Edited Date: 2017-02-26 15:50 (UTC)

Date: 2017-02-26 20:09 (UTC)
From: [personal profile] caztd
да я думаю, если даже взять обычный канон текст и gcc и собрать статически, то результат тоже будет очень неплох (несколько кб емнип). Да и с плюсами результат должен быть идентичный.

Date: 2017-02-27 19:07 (UTC)
From: [personal profile] caztd
Надо смотреть, что линкер в бинарник по умолчанию кидает и нужно ли это все.
То есть понятно, что printf за собой потянет воз, но не настолько много.
Кроме того gcc умеет вставлять вместо printf build-in'ы, которые используют puts.
Не знаю, насколько можно убрать оттуда malloc, который для буфера используется --
но там конечно тоже есть варианты. При желании можно достаточно сложную логику
впихнуть в 64k, вместе с malloc'ом и прочим stdio (и даже GUI).
Так что тут вопрос только в оптимизации бинарника.
Но это все делается обычно только для embedded devices.

На десктопе конечно никто килобайты не считает, поэтому в память грузится вся libc,
а у плюсов еще и libstdc++ -- оно конечно же имеет смысл, поскольку это память общая
для всех процессов и лучше загрузить один раз, чем статически собирать бинарники
и грузить при каждом запуске.
Сравнивать же resident size в таком контексте смысла не имеет вообще.
Тогда уж лучше смотреть CODE+DATA:
https://stackoverflow.com/questions/7594548/res-code-data-in-the-output-information-of-the-top-command-why

Date: 2017-02-27 05:34 (UTC)
dadv: (Default)
From: [personal profile] dadv
Для разработки несложных command line утилит вроде бы давным-давно используются скриптовые языки, начиная с POSIX shell, используемые которым код и библиотеки загружены в кеш / отмаплены в память ещё с момента загрузки системы.

Date: 2017-03-11 20:11 (UTC)
netch80: (Default)
From: [personal profile] netch80
А если таких бинарей разных будет запущено много, то у остальных общие ресурсы типа libc будут представлены в памяти одним экземпляром, а у Go и прочих статически собранных - по экземпляру на каждый результат линковки.
Где это лучше или хуже - предсказать сразу сложно, но такой бенчмарк тоже необходим.