Подтвердить что ты не робот

D без сборщика мусора

Я смотрю на D сегодня, и на поверхности это выглядит довольно удивительно. Мне нравится, как он включает в себя множество конструкций более высокого уровня непосредственно на этом языке, поэтому не нужно использовать глупые хаки или сложные методы. Одна вещь, которая действительно беспокоит меня, если GC. Я знаю, что это большие проблемы, и я много читал об этом.

Мои собственные простые тесты, спроецированные на вопрос, показывают, что GC очень медленный. Более 10 раз медленнее, чем прямой С++, делающий то же самое. (очевидно, тест напрямую не преобразуется в реальный мир, но удар по производительности является экстремальным и замедляется, когда происходит реальный мир, который ведет себя одинаково (быстро выделяя множество мелких объектов)

Я занимаюсь написанием звукового приложения с низкой задержкой в ​​реальном времени, и возможно, что GC испортит производительность приложения, чтобы сделать его почти бесполезным. В каком-то смысле, если у него есть какие-либо проблемы, это разрушит аудиоспектр в реальном времени, что намного более важно, поскольку в отличие от графики звук работает с гораздо более высокой частотой кадров (44000+ против 30-60). (из-за его низкой латентности он более важен, чем стандартный аудиоплеер, который может накапливать значительные объемы данных).

Отключение GC улучшило результаты примерно до 20% кода С++. Это важно. Я дам код в конце для анализа.

Мои вопросы:

  • Насколько сложно заменить D GC стандартной реализацией интеллектуальных указателей, чтобы библиотеки, которые полагались на GC, все еще можно было использовать. Если я полностью удалю GC, я потеряю много работы, так как у D уже есть библиотеки ограничений по сравнению с С++.
  • Может ли GC.Disable временно приостанавливать сборку мусора (предотвращение запуска потока GC) и GC.Enable выбрать резервную копию там, где она была остановлена. Поэтому я могу потенциально отключить GC от работы в высокопроизводительных моментах, чтобы предотвратить проблемы с задержкой.
  • Есть ли способ заставить шаблон не использовать GC последовательно. (это потому, что я не программировал в D, и когда я начинаю писать очки, которые не используют GC, я бы хотел быть уверен, что не забываю реализовать их собственную очистку.
  • Можно ли легко заменить GC в D? (не то, что я хочу, но может быть интересно поиграть с разными методами GC в один прекрасный день... это похоже на 1, я полагаю)

То, что я хотел бы сделать, это торговля памятью для скорости. Мне не нужно, чтобы GC запускался каждые несколько секунд. На самом деле, если я смогу правильно реализовать свое собственное управление памятью для своих структур данных, то, скорее всего, он не будет запускаться очень часто. Возможно, мне придется запускать его, только когда память становится скудной. Однако из того, что я прочитал, чем дольше вы дожидаетесь, тем быстрее это будет. Поскольку в моем приложении обычно бывают случаи, когда я могу уйти с вызовом без проблем, это поможет смягчить некоторое давление (но опять же, могут быть часы, когда я не смогу назвать это).

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

Из того, что я прочитал, я могу, по крайней мере, пойти по пути C/С++, пока я не использую никаких библиотек или языковых конструкций, которые полагаются на GC. Проблема в том, что я не знаю тех, которые делают. Я видел строку, новую и т.д., Но означает ли это, что я не могу использовать строковые строки, если я не включаю GC?

Я читал в некоторых сообщениях об ошибках, что GC может быть действительно глючным, и это может объяснить его проблемы с производительностью?

Кроме того, D использует немного больше памяти, на самом деле D заканчивается из памяти перед программой на С++. Я предполагаю, что это примерно на 15% больше или около того в этом случае. Я полагаю, что это для GC.

Я понимаю, что следующий код не является репрезентативным для вашей средней программы, но то, что он говорит, заключается в том, что когда программы создают множество объектов (скажем, при запуске), они будут намного медленнее (10 раз - большой фактор). Из GC можно "приостановить" при запуске, тогда это не обязательно будет проблемой.

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

например.

{
    Foo f = new Foo();
    ....
    dispose f; // Causes f to be disposed of immediately and treats f outside the GC
               // If left out then f is passed to the GC.
               // I suppose this might actually end up creating two kinds of Foo 
               // behind the scenes. 

    Foo g = new manualGC!Foo();   // Maybe something like this will keep GC hands off 
                                  // g and allow it to be manually disposed of.
}

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

код:

module main;
import std.stdio, std.conv, core.memory;
import core.stdc.time;

class Foo{
    int x;
    this(int _x){x=_x;}
}

void main(string args[]) 
{

    clock_t start, end;
    double cpu_time_used;


    //GC.disable();
    start = clock();

    //int n = to!int(args[1]);
    int n = 10000000;
    Foo[] m = new Foo[n];

    foreach(i; 0..n)
    //for(int i = 0; i<n; i++)
    {
        m[i] = new Foo(i);
    }

    end = clock();
    cpu_time_used = (end - start);
    cpu_time_used = cpu_time_used / 1000.0;
    writeln(cpu_time_used);
    getchar();
}

Код С++

#include <cstdlib>
#include <iostream>
#include <time.h>
#include <math.h>
#include <stdio.h>

using namespace std;
class Foo{
public:
    int x;
    Foo(int _x);

};

Foo::Foo(int _x){
    x = _x;
}

int main(int argc, char** argv) {

    int n = 120000000;
    clock_t start, end;
    double cpu_time_used;




    start = clock();

    Foo** gx = new Foo*[n];
    for(int i=0;i<n;i++){
        gx[i] = new Foo(i);
    }


    end = clock();
    cpu_time_used = (end - start);
    cpu_time_used = cpu_time_used / 1000.0;
    cout << cpu_time_used;

    std::cin.get();
    return 0;
}
4b9b3361

Ответ 1

  • D может использовать практически любую библиотеку C, просто определите необходимые функции. D также может использовать библиотеки С++, но D не понимает некоторые конструкции С++. Поэтому... D может использовать почти столько же библиотек, сколько С++. Они просто не являются родными D libs.

  • От ссылки D библиотеки.
    Core.memory:

    static nothrow void disable();
    

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

    static pure nothrow void free(void* p);
    

    Отменяет память, на которую ссылается p. Если p равно null, никаких действий не происходит. Если p ссылается на память, первоначально не выделенную этим сборщиком мусора, или если она указывает на внутреннюю часть блока памяти, никаких действий не будет предпринято. Блок не будет завершен независимо от того, установлен ли атрибут FINALIZE. Если требуется финализация, вместо этого используйте delete.

    static pure nothrow void* malloc(size_t sz, uint ba = 0);
    

    Запросит выровненный блок управляемой памяти из сборщика мусора. Эта память может быть удалена по желанию с помощью бесплатного вызова или может быть автоматически удалена и очищена во время запуска коллекции. Если распределение не выполняется, эта функция вызовет функциюOutOfMemory, которая, как ожидается, вызовет OutOfMemoryError.

    Так что да. Подробнее здесь: http://dlang.org/garbage.html

    И здесь: http://dlang.org/memory.html

    Если вам действительно нужны классы, посмотрите на это: http://dlang.org/memory.html#newdelete delete был устарел, но я считаю, что вы все равно можете его освободить.

  • Не используйте классы, используйте structs. Структуры выделены в стек, классы - в кучу. Если вам не нужна поддержка полиморфизма или других вещей, они накладные расходы на то, что вы делаете. Вы можете использовать malloc и бесплатно, если хотите.

  • Более или менее... заполните здесь определения функций: https://github.com/D-Programming-Language/druntime/blob/master/src/gcstub/gc.d. Там создана прокси-система GC, позволяющая настроить GC. Так что это не нравится, что дизайнеры не хотят, чтобы вы это делали.

Маленькое знание GC здесь: Сборщик мусора не гарантирует запуск деструктора для всех объектов без ссылок. Кроме того, порядок, в котором сборщик мусора вызывает деструкторы для объектов без ссылки, не указан. Это означает, что когда сборщик мусора вызывает деструктор для объекта класса, у которого есть члены, которые являются ссылками на собранные мусором объекты, эти ссылки могут быть более недействительными. Это означает, что деструкторы не могут ссылаться на вспомогательные объекты. Это правило не применяется к автообъектам или объектам, удаленным с помощью DeleteExpression, поскольку деструктор не запускается сборщиком мусора, что означает, что все ссылки действительны.

import std.c.stdlib; который должен иметь malloc и бесплатно.

import core.memory; это имеет GC.malloc, GC.free, GC.addroots,//добавляет внешнюю память в GC...

Строки

требуют GC, потому что они являются динамическими массивами неизменяемых символов. (неизменяемый (char) []) Динамические массивы требуют GC, статические - нет.

Если вы хотите ручное управление, продолжайте.

import std.c.stdlib;
import core.memory;

char* one = cast(char*) GC.malloc(char.sizeof * 8);.
GC.free(one);//pardon me, I'm not used to manual memory management. 
//I am *asking* you to edit this to fix it, if it needs it.

зачем создавать класс-оболочку для int? вы делаете не что иное, как замедление и истощение памяти.

class Foo { int n; this(int _n){ n = _n; } }
writeln(Foo.sizeof);  //it 8 bytes, btw
writeln(int.sizeof);  //Its *half* the size of Foo; 4 bytes.


Foo[] m;// = new Foo[n]; //8 sec
m.length=n; //7 sec minor optimization. at least on my machine.
foreach(i; 0..n)
    m[i] = new Foo(i);


int[] m;
m.length=n; //nice formatting. and default initialized to 0
//Ooops! forgot this...
foreach(i; 0..n)
    m[i] = i;//.145 sec

Если вам действительно нужно, тогда напишите функцию Time-sensitive в C и вызовите ее из D. Черт, если время действительно велико для сделки, используйте встроенную сборку D для оптимизации всего.

Ответ 2

Я предлагаю вам прочитать эту статью: http://3d.benjamin-thaut.de/?p=20 Там вы найдете версию стандартной библиотеки, которая выполняет собственное управление памятью и полностью избегает сбора мусора.

Ответ 3

D GC просто не так изощрен, как другие, такие как Java. Это с открытым исходным кодом, поэтому каждый может попытаться его улучшить.

Существует экспериментальный параллельный GC с именем CDGC и существует текущий проект GSoC для удаления глобальной блокировки: http://www.google-melange.com/gsoc/project/google/gsoc2012/avtuunainen/17001

Обязательно используйте LDC или GDC для компиляции, чтобы получить лучший оптимизированный код.

В проекте XomB также используется пользовательское время выполнения, но это D версия 1, я думаю. http://wiki.xomb.org/index.php?title=Main_Page

Ответ 4

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

И, кстати, это не так медленно, как вы упомянули. И GC.disable() действительно не отключает его.

Ответ 5

Мы можем рассмотреть проблему с немного другого вида. Субоптимальная производительность выделения многих маленьких объектов, которые вы упоминаете как обоснование вопроса, имеет мало общего с GC. Скорее, это вопрос баланса между универсальными (но субоптимальными) и высокоэффективными (но специализированными) средствами управления памятью. Идея такова: наличие GC не мешает вам писать приложение в реальном времени, вам просто нужно использовать более конкретные инструменты (скажем, пулы объектов) для особых случаев.

Ответ 6

Так как это еще не было закрыто, в последних версиях D есть библиотека std.container, которая содержит структуру данных Array, которая значительно эффективнее по отношению к памяти, чем встроенные массивы. Я не могу подтвердить, что другие структуры данных в библиотеке также эффективны, но, возможно, стоит заглянуть, если вам нужно больше осознавать память, не прибегая к ручному созданию структур данных, которые не требуют сбора мусора.