LINUX.ORG.RU

Контейнеры в C


2

3

Будучи сиплюсплюсником, меня давно интересует как те же задачи выполняются в C. Знаю, на ЛОРе есть множество приверженцев C, надеюсь они ответят на пару простых вопросов.

Я являюсь активным сторонником идеи «Алгоритм должен работать настолько быстро, насколько позволяет железо». С этой точки зрения C++ даёт потрясающие возможности из-за своей системы кодогенерации. Да, я имею в виду шаблоны.

Не будем зарываться в дебри boost'а, возьмём простую задачу - контейнеры.

Для примера я взял односвязные списки в GTK GSList и Qt QList. QList позволяет хранить как простые, так и сложные типы с конструкторами, деструкторами, типы с общими данными. При этом накладных расходов на выделение в куче не происходит, а при реаллокации выбирается нужный алгоритм в зависимости от QTypeInfo<T>, к примеру для int будет вызван memcpy(), а для QString оператор копирования. Кроме того блок данных заранее резервируется и для сложных типов вызывается placement constructor. Для удаления данных по указателям используется алгоритм qDeleteAll(from,to), который сам дёрнет нужные конструкторы. Беглый просмотр GSList показал, что в нём можно хранить только указатели, со всеми вытекающими накладными расходами на аллокацию/уничтожение памяти, ручное кастирование, слежение за утечками, фрагментацию памяти.

Аналогичная ситуация со связными списками GList и QLinkedList.

Это даже не затрагивая вопрос о типизации, в C++ компилятор сразу даст по рукам при попытке записать в контейнер неверный тип или с помощью неявного преобразования с explicit-конструктором.

Далее, счётчики ссылок и деструкторы.

К примеру, в C++ список строк будет выглядеть как QList<QString>, при этом можно забыть про внутреннюю структуру строки - конструктор, деструктор и операторы копирования сами занимаются подсчётом ссылок, разделением и уничтожением данных. Вернуть строку из функции проще простого:

QString foo()
{
    return listOfStrings.at( 5 );
}

Этот код абсолютно безопасен и оптимален с точки зрения использования памяти и процессора, поскольку время тратится только на увеличение счётчика и копирование указателя.

В GTK я сходу нашёл GString:

struct GString 
{
  gchar *str;
  gint len;
};

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

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

Или может GTK неудачный пример, тогда дайте ссылку на правильную C-библиотеку с контейнерами.

★★★★★
Ответ на: комментарий от ahonimous

да «читы» в С++ безусловно на высоте :) другое дело почему ими так мало пользуются? Любая большая (over 9000) библиотека обязательно включает в себя ту или иную реализацию фундаментальных алгоритмов разной степени качества. Тоже самое в C есть множество «читов» использование которых может далеко оставить позади STL. И всё равно в каждой большой библиотеке мы найдем пару крайне тупых реализаций различных CList GObject и т.п :)

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

> да «читы» в С++ безусловно на высоте :) другое дело почему ими так мало пользуются? Любая большая (over 9000) библиотека обязательно включает в себя ту или иную реализацию фундаментальных алгоритмов разной степени качества.

NIH - это и для С свойственно

Тоже самое в C есть множество «читов» использование которых может далеко оставить позади STL


вот только эти «читы» в С делают код запутанней и сложней для понимания, и не применимы в общем случае, и в С++ в случае чего можно тоже узкие места написать c этими «читами», при этом в основной части кода используя простой и быстрый stl

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

> NIH - это и для С свойственно

да ладно в библиотеках на C++ различных CList<Datatype> over 9000

вот только эти «читы» в С делают код запутанней и сложней для понимания, и не применимы в общем случае

всё зависит от умения

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

> да ладно в библиотеках на C++ различных CList<Datatype> over 9000

google: linked list library C

всё зависит от умения


в любом языке, но проще один раз выучить stl, чем разбираться каждый раз в новом велосипеде очередного «умельца»

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

Раз уж пошла такая пъянка, вот предлагаю обсудить интересный контейнер, http://en.wikipedia.org/wiki/Judy_array

простой код

#include <stdio.h>
#include <unistd.h>
#include <string.h>

#include <Judy.h>

// By Doug Baskins Aug 2002 - for JudySL man page

int     // Usage:  JudySort < file_to_sort
main()
{
    Pvoid_t   PJArray = (PWord_t)NULL;  // Judy array.
    PWord_t   PValue;                   // Judy array element.
    Word_t    Bytes;                    // size of JudySL array.
    char      Index[BUFSIZ];            // string to sort.

    while (fgets(Index, sizeof(Index), stdin) != (char *)NULL)
    {
        JSLI(PValue, PJArray, Index);   // store string into array
        ++(*PValue);                    // count instances of string
    }
    Index[0] = '\0';                    // start with smallest string.
    JSLF(PValue, PJArray, Index);       // get first string
    while (PValue != NULL)
    {
        while ((*PValue)--)             // print duplicates
            printf("%s", Index);
        JSLN(PValue, PJArray, Index);   // get next string
    }
    JSLFA(Bytes, PJArray);              // free array

    fprintf(stderr, "The JudySL array used %lu bytes of memory\n", Bytes);
    return (0);
}

по моим замерам в пять раз быстрее аналогичного решения на std::map

#include <iostream>
#include <string>
#include <map>
using namespace std;


int main()
{
 typedef map<string, int> SortMap;
 SortMap strs;

 string str;
 while(getline(cin, str))
 {
  strs[str]++;
 }

 SortMap::iterator i = strs.begin();
 while(i != strs.end())
 {
  cout << (*i++).first << endl;
 }

}

а на больших объёмах информации в два раза быстрей стандартного sort

кто больше? :)

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

> std::hash_map несортируется поэтому будет только хуже :)

у меня есть обоснованное мнение, что вы в своем тесте только сортировку и меряли

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

Judy arrays are designed to keep the number of processor cache-line fills as low as possible, and the algorithm is internally complex in an attempt to satisfy this goal as often as possible. Due to these cache optimizations, Judy arrays are fast, sometimes even faster than a hash table, especially for very big datasets.

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

Да померял только сортировку но у меня есть обоснованое мнение что этот контейнер «порвет» по скорости и по памяти любую поделку из std::* :D

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

> как бы этим все сказано

нет не всё есть еще приписка «especially for very big datasets» это становится актуальным даже для десктопов (современных)

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

> Да померял только сортировку но у меня есть обоснованое мнение что этот контейнер «порвет» по скорости и по памяти любую поделку из std::* :D

вот результат сравнения с хэштаблицей:

http://nothings.org/computer/judy/#results

не видно никого «прорыва», ну и для С++ есть:

http://judyhash.sourceforge.net/

если уж сильно хочется

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

> есть конкретная задача - есть однозначные результаты по нему

Глазками пользоваться умеем? И потом ещё думать (для этого нужен работающий мозг)? Выше приведён ряд замеров, по которым всё вразнобой на трёх системах (две моих + одна ваша) в зависимости от версии компилятора и даже без ключей компиляции. О какой однозначности тут можно говорить? Разве что о порождённой вашим фанатизмом и живущей только в вашем воображении. :)

Результаты не только не подтверждают мою точку зрения - такие разнобойные результаты ни подтверждают, ни опровергают вообще что-либо по обсуждаемому вопросу. И делать какие-то заявления, отталкиваясь от таких результатов, imho, идиотизм. Так - понятно?

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

> Глазками пользоваться умеем? И потом ещё думать (для этого нужен работающий мозг)?

схб

Выше приведён ряд замеров, по которым всё вразнобой на трёх системах (две моих + одна ваша) в зависимости от версии компилятора и даже без ключей компиляции.

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



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

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

несовсем корректно сравнивать judy с просто хеш таблицей потомучто в нем ключи хранятся в отсортированом порядке т.е. тут у нас получается какгбы бесплатная сортировка во вторых judy более экономно расходует память, почему то привереженцы ++ постоянно забывают про память, хотя это и понятно обычно программы на C++ сравнимы с джавай: захапать всю оперативную память по разный «шлаг» вроде RTTI а данные пользователя засунуть в swap

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

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

внутренняя реализация нас не должна волновать

во вторых judy более экономно расходует память


опять же - его можно использовать и в С++

почему то привереженцы ++ постоянно забывают про память


помнят, но экономить память надо там, где это действительно надо - и если у меня есть список на 200 элементов, то я лучше возьму обычный map/hash_map чем буду подключать немаленькую библиотеку, вроде Judy, задачи с действительно огромными данными не часто встречаются - и к ним совсем другой подход

обычно программы на C++ сравнимы с джавай: захапать всю оперативную память по разный «шлаг» вроде RTTI


можно конкретней - у меня например Gnome более прожорлив, чем КДЕ, а какие программы на С++ у вас больше едят памяти?

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

можно конкретней

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

опять же - его можно использовать и в С++

как вы правильно заметили C код можно использовать в C++ берем обёртку на C++ вокруг кода на C взято отсюда http://sourceforge.net/projects/judyhash

#include <iostream>
#include <string>

#include "hash_funcs.h"
#include "judy_map.h"
using namespace std;

int main()
{
 typedef judy_map_l< string, int, hashfunc_poly <65599> > SortMap;
 SortMap strs;

 string str;
 while(getline(cin, str))
 {
  strs[str]++;
 }

 SortMap::iterator i = strs.begin();
 while(i != strs.end())
 {
  cout << (*i++).first << endl;
 }
}

почему то я получаю тоже отставание практически в пять раз на больших данных по сравнению с C, обертка вокруг judy работает примерно также как и std::map :( кто виноват и что делать?

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

прошу прощения собирал без оптимизации с флагом -O2 разница появилась, теперь отставание от C в четыре раза

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

У с++ вывод небуферизированный. Если лень курить ман (и шерстить ЛОР) на тему включения буферизации, то переделайте на аботу с двумя файлами.

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

Gnome это отвратительный пример библиотеки написанной на C, какой хороший? ну некоторые драйвера в linux похожи на произведения искусства :) эслиб эти люди заинтересовались бы написанием GUI библиотек... эх..

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

в варианте с файлами отставание C++ несколько увеличилось думаю это из за объектоно-орентированой обертке вокруг файлов :D

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

к слову говоря - iostream большинство сиплюсников не любит, он ненужная сущность - проще и удобней пользоваться stdio

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

gcc не торт да, ну давайте протестируем на Visual Studio Express оно тоже бесплатное >_< только у меня постоянно проблемы с этими окнами и диалогами там

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

разница не из-за ввода-вывода в примерах есть отличие в варианте C кода выводятся повторы

while ((*PValue)--)             // print duplicates 
            printf("%s", Index); 
а в варианте с C++ нет я тестировал на тексте войны и мира Толстого повторенного несколько раз так что повторов там было много. С дает фору C++ :)

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

>разница не из-за ввода-вывода

Из-за него родимого. Либо cout надо настроить либо ofstream использовать. На лоре это уже обсуждалось, разница существенная. Без этого просто нет смысла сравнивать.

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

> а в варианте с C++ нет я тестировал на тексте войны и мира Толстого повторенного несколько раз так что повторов там было много

не знаю правильно ли я понял - но имеешь ввиду, что в коде на С++ лишие действия - проверка на дублирование при вставке? ну так очевидно, что это требует времени и выполненые задачи не равнозначны

С дает фору C++ :)


C++ дает возможность использовать любую библиотеку на С - что соб-сно делается достаточно часто

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

>C++ дает возможность использовать любую библиотеку на С - что соб-сно делается достаточно часто
Я тебе по секрету скажу, это практически любая реализация практически любого языка позволяет делать.

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

> Я тебе по секрету скажу, это практически любая реализация практически любого языка позволяет делать.

а я тебе по секрету отвечу, что в отличие от этих языков - в С++ это делается «прямо» с помощью обычного #include

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

На C делают конфетки

На C++ конфетки заворачивают в красивые обертки

На Джава их едят и разводят тупых юзеров на бабло :D

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

не верю в то, что в плюсах это есть, а в си нет

Не верите что в си нет шаблонов? :) Или что использование шаблонов приводит к большей производительности? Вот тут http://theory.stanford.edu/~amitp/rants/c++-vs-c/, например, человек приходит к такому выводу:

I can’t claim that C++ is always faster than C. However, I do claim that C++ templates offer a way to provide library routines that offer the traditional advantages of library routines (ease of use, flexibility) yet still are able to provide speed close to hand-written code. This combination is generally unavailable to programmers using C (or Pascal, or Basic, or Java, or …).

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

да в C нет шаблонов поэтому их можно прикручивать как угодно начиная от M4 и заканчивая perl, конечно они будут не type safe но это никогда и ни считалось в C чем то нужным :) зато они будут гораздо гибче

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

если ты ожидал одинаковые цифры на разных компиляторах, платофрмах и машинах - то ты идиот

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

> gcc -o c_list list.c
> time ./c_list some_nick 10000000 
 
real    0m1.067s 
user    0m0.696s 
sys     0m0.348s

> gcc -o c_list -O3 list.c
> time ./c_list some_nick 10000000

real    0m1.374s
user    0m0.944s
sys     0m0.408

Это в одной и той же системе и на одном и том же железе, без перезагрузок и обновлений между тестированиями. Почему такое - пока ещё не понял, но факт имеется. В другой моей системе такого не наблюдается, а в «замедляющей» я сам не пересобирал с какими-нибудь хитрыми опциями ни компилятор, ни библиотеки - в ней Debian Lenny 5.0.4 «из коробки», обновлялся с официальных реп при помощи apt.

Заметь, кстати, я нигде не писал, что один из языков рулит, а другой - отстой, более того, в той моей системе, которая «нормальная», моя C-программа работает медленнее программы на C++ и эти замеры я тоже опубликовал.

_для меня_ это - «разнобой» и до выяснения причины такого поведения «замедляющей» системы я с выводами подожду.

А плюсанутые яблочники все такие или ты один за весь «вид» отдуваешься?

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

Я знаю такое мнение - что шаблоны это просто препроцессор и их можно прикрутить к Си так же. Это не совсем так - шаблоны это часть компилятора (которую нельзя «вырвать»), так что чтобы реально сделать compile-time шаблоны (которые тьюринг-полны) в Си - нужно написать на нём компилятор си-с-шаблонами - т.е. то чем помимо прочего является С++.

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

Речь не о том что Си в целом медленнее С++ - он медленнее в сфере некоторых оптимизаций, а точнее:

*) В С++ есть возможность compile-time диспетчеризация типа в шаблоне.

*) В Си подобная диспансеризация возможна только в run-time -> а это лишние инструкции -> замедление.

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

По поводу читинга появилась пара мыслей:

- раз уж мы в C-коде принудительно чистим память, то это же нужно делать и коде C++, т.е. перед закрывающей скобкой нужно добавить что-то вроде list.erase(list.begin(), list.end());

- в коде на C++ память выделяется сразу под весь список, а не под каждый элемент в теле цикла. Доработал под это дело и свой C-код:

#include <stdlib.h> 
#include <string.h> 

struct person {
    struct person *first;
    struct person *next;
    struct person *prev;
    char *nick;
    int id;
};

int main(int argc, char *argv[]) {
    struct person *p, *first, *last, *prev;

    int structSize = sizeof(struct person);
    int i = 1, step = structSize;
    int nameLen = strlen(argv[1]);
    int count = atoi(argv[2]);
    void *listArea = malloc(count * structSize);

    first = (struct person*) listArea;
    first->prev = NULL;
    first->nick = calloc(1, nameLen + 1);
    memcpy(first->nick, argv[1], nameLen);
    first->id = 0;
    prev = first;
    while (i < count) {
        p = (struct person*) (listArea + step);
        p->first = first;
        p->next = NULL;
        p->prev = prev;
        prev->next = p;
        p->nick = calloc(1, nameLen + 1);
        memcpy(p->nick, argv[1], nameLen);
        p->id = i;
        prev = p;
        i++;
        step += structSize;
    }
    p = first;
    do {
        free(p->nick);
        p = p->next;
    } while (NULL != p);
    free(listArea);
}

Замеры на моей «незамедляющей» системе:

> gcc -o c_list list.c
> time ./c_list dendy 10000000

real    0m2.636s
user    0m1.910s
sys     0m0.650s
> time ./c_list dendy 10000000

real    0m2.557s
user    0m1.917s
sys     0m0.640s
> time ./c_list dendy 10000000

real    0m2.579s
user    0m1.943s
sys     0m0.611s
> gcc -o c_list_opt -O3 list.c
> time ./c_list_opt dendy 10000000

real    0m2.171s
user    0m1.496s
sys     0m0.616s
> time ./c_list_opt dendy 10000000

real    0m2.012s
user    0m1.383s
sys     0m0.629s
> time ./c_list_opt dendy 10000000

real    0m2.190s
user    0m1.465s
sys     0m0.674s
> g++ -o cpp_list list.cpp
> time ./cpp_list dendy 10000000

real    0m4.081s
user    0m3.554s
sys     0m0.400s
> time ./cpp_list dendy 10000000

real    0m3.838s
user    0m3.413s
sys     0m0.402s
> time ./cpp_list dendy 10000000

real    0m3.932s
user    0m3.431s
sys     0m0.426s
> g++ -o cpp_list_opt -O3 list.cpp
> time ./cpp_list_opt dendy 10000000

real    0m2.259s
user    0m1.781s
sys     0m0.404s
> time ./cpp_list_opt dendy 10000000

real    0m2.220s
user    0m1.773s
sys     0m0.419s
> time ./cpp_list_opt dendy 10000000

real    0m2.123s
user    0m1.673s
sys     0m0.421s

Мне осталось только понять, что не так с моей «замедляющей» системой.

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

>На одной из моих систем добавление ключа оптимизации замедляет собранную C-программу примерно на треть

-O3

Почему такое - пока ещё не понял

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

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

> Сынок, прежде чем лезть в такие флеймы помолчи и почитай

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

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

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

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

Процессор вполне себе обычный - Core2 Duo E8400. Сегодня я этот эффект вопроизвести не могу, что заставляет думать, что проблема всё же порождена человеческим фактором, например, моей невнимательностью.

Ну и я не против широко доступных в наше время хитрых программно-аппаратных сочетаний «проц-память-код», влияющих на производительность, но не на 30% же. Это можно было бы объяснить, например, медленным ЖД или его контроллером, там это ещё возможно, но современных работоспособных комбинаций «типпроца-типпамяти-чипсет» совсем немного и они чётко заданы производителями железа. Где тут взять 30%?

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

>сочетаний «проц-память-код», влияющих на производительность, но не на 30% же

Да запросто. Я же говорю, если данные умещаются в кеш - прирост может быть в разы. Пруфы доставлять лень, припоминаю презентацию про оптимизацию плюсоигры для плейстейшн. Там заменили массив объектов на несколько массивов тривиальных данных и поменяли порядок вызова функций чего-то там, получили ускорение, емнип, в 3 раза.

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

gcc никогда не славился своим оптимизатором, так что ничего удивительного в том что ключи оптимизации могут приводить не только к замедлению но и к гораздо более интересным последствием типа undefined behavior, что тут спорить? Такой флейм не интересен.

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

проблема высосана из пальца - никто так не пишет на С, достаточно погуглить по полученной ошибке:

google: «error: expected unqualified-id before ‘new’»
20 результатов

google: «error: expected unqualified-id before ‘template’»
27 результатов

бегло просмотрев - большая часть из них в плюсовом же коде у новичков, еще половина - компиляция .c файлов с помощью g++, и только в одном случае - именно включение .h файла( и не из библиотеки )

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

Это на C++ так никто не пишет. По понятным причинам, гг.
Проблема в том, что Си - не подмножество C++. И начиная даже не с C99, а много раньше.

И, кстати, я еще нейм мэнглинг не упомянул.

Суть в том, что если сишные заголовочные файлы не обработаны под C++ - с кучей #define, extern «C», неиспользованием плюсовских идентификаторов и соблюдением плюсовских конвенций именования(и в этом случае, если они обработаны, это, по факту, уже C++), то включать их в C++ код в общем случае нельзя.

Так что пук про #include приходится в лужу.

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

> Так что пук про #include приходится в лужу.

по-моему «пукаешь» тут ты - давай конкретный пример несовместимой библиотеки или перестань фантазировать

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