Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Указатели неправильно блокируются, что ломает любой код с ними #1155

Closed
SunSerega opened this issue Sep 4, 2018 · 9 comments

Comments

@SunSerega
Copy link
Contributor

begin
  System.GC.Collect;
  readln;
  loop 1024*1024 do
  begin
    var a := new byte[1024];
    var ptr := @a[0];
    ptr^ := 0;
  end;
  System.GC.Collect;
  readln;
end.

Эта программа сжирает 1 гигабайт памяти и отказывается его отдавать на System.GC.Collect.
Но, что важнее - эта программа иногда выдаёт System.AccessViolationException!
Смотрим в декомпилированный вариант:

    public static unsafe void $Main()
    {
      int num1 = 1048576;
      int num2 = 1;
      if (num2 > num1)
        return;
      while (true)
      {
        byte[] numArray = new byte[1024];
        GCHandle.Alloc((object) numArray);
        byte* numPtr = &numArray[0];
        *numPtr = (byte) 0;
        if (num2 < num1)
          ++num2;
        else
          break;
      }
    }

Значит проблемы 2:

  • GCHandle.Alloc((object) numArray) - это мало. Правильно - GCHandle.Alloc((object) numArray, GCHandleType.Pinned), без этого будем получать System.AccessViolationException при сборке мусора.
    Как сказано на msdn - если не указать какой тип блокировки - выбирается обычная, которая лишь не позволяет сборщику мусора удалить его, но позволяет перемещать.

  • Блокировка не освобождается. Надо присваивать результат GCHandle.Alloc переменной, и вызывать для неё Free в конце блока кода, в котором объявлен указатель. При чём, это обязательно делать в try finally.
    Если не освободить блокировку - получаем утечки памяти, отсюда тот не высвобождаемый гигабайт.
    Если не ставить в try finally - тоже получаем утечки памяти. Но только если будет исключение до высвобождения блокировки.

@SunSerega
Copy link
Contributor Author

SunSerega commented Sep 5, 2018

P.S. исправьте, пожалуйста, побыстрее - это ломает так же и BlockFileOf<T>. Я сегодня перепробовал ещё несколько способов, как пока что обойти - но ни 1 из способов копирования памяти без указателей не работает.

@ibond84
Copy link
Contributor

ibond84 commented Sep 5, 2018

Вы что, хотите сборщика мусора для указателей? Не получится. Указатели на управляемые участки кода не разблокируются до окончания программы. Это естественное и очевидно ограничение. Блокируйте указатель явно (как этo в C#). fixed в паскале нет. Но можно вызывать явно GCHandle.Alloc и GCHandle.Free

@ibond84 ibond84 closed this as completed Sep 5, 2018
@SunSerega
Copy link
Contributor Author

SunSerega commented Sep 5, 2018

Указатели на управляемые участки кода не разблокируются до окончания программы

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

можно вызывать явно GCHandle.Alloc и GCHandle.Free

GCHandle.Free это не статичная процедура, её надо вызывать для переменной, полученной через GCHandle.Alloc, а так как она добавляется компилятором - это невозможно.

Более того, из за того что вы Не добавили GCHandleType.Pinned - блокировка которая сейчас выполняется вами - не даёт никакого результата, кроме утечек памяти.

Если так уж хотите спихнуть блокируку указателей на ручное действие пользователей - уберите блокировку которая сейчас, потому что она обнуляет ручную. То есть если я напишу так:

uses System.Runtime.InteropServices;

begin
  System.GC.Collect;
  readln;
  loop 1024*1024 do
  begin
    var a := new byte[1024];
    var gc_hnd := GCHandle.Alloc(a, GCHandleType.Pinned);
    try
      var ptr := @a[0];
      ptr^ := 0;
    finally
      gc_hnd.Free;
    end;
  end;
  System.GC.Collect;
  readln;
end.

Эта программа всё равно сжирает 1 гиг оперативки и всё равно временами выдаёт System.AccessViolationException.

По крайней мере, если убрать блокировку что сейчас - ничего не сломается, что ещё не сломано.
Зато не будет утечек памяти. И будет работать ручная блокировка.

@ibond84
Copy link
Contributor

ibond84 commented Sep 5, 2018

Надо делать так:

uses System.Runtime.InteropServices;

begin
  System.GC.Collect;
  readln;
  loop 1024*1024 do
  begin
    var a := new byte[1024];
    var gc_hnd := GCHandle.Alloc(a, GCHandleType.Pinned);
    try
      var ptr := pbyte(pointer(gc_hnd.AddrOfPinnedObject()));
      ptr^ := 0;
    finally
      gc_hnd.Free;
    end;
  end;
  System.GC.Collect;
  readln;
end.

И будет занимать 5 мб памяти.

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

@SunSerega
Copy link
Contributor Author

SunSerega commented Sep 5, 2018

тогда сборщик мусора еще быстрее переместит объект.

Нет не быстрее, потому что, повторюсь, В БЛОКИРОВКЕ КОТОРАЯ СЕЙЧАС НЕ УКАЗАНО GCHandleType.Pinned. Она уже ничего не блокирует в плане перемещение, поэтому если убрать её - ничего не произойдёт.

А то что предложили вы - это очень много лишний действий, точнее преобразование + вызов метода, что негативно повлияет на производительность.

@ibond84
Copy link
Contributor

ibond84 commented Sep 5, 2018

Я вам предложил единственно верный вариант, который будет работать прозрачно и быстро. И переместит быстрее, это проверено на опыте. Делайте, как я сказал.

@SunSerega
Copy link
Contributor Author

SunSerega commented Sep 5, 2018

единственно верный вариант, который будет работать прозрачно и быстро

Ваш вариант использует вызов метода + 2 вызова IL оператора box.
В том время как @ это 1 оператор в IL коде, на прямую получающий ссылку.

И всё равно, оригинальные указатели значит сломаны, они могут вызвать ошибки. Почему вы не хотите убрать блокировку, если она не постоянно работает? Если уже не хотите исправить (что не должно быть сложно). Чтоб хотя бы можно было реализовать действительно быстро.

@SunSerega
Copy link
Contributor Author

И, кстати, это не то что надо опытом и "Я сказал, делайте как я сказал" доказывать, предоставьте тесты, в которых без недоблокировки System.AccessViolationException будет чаще.

@ibond84
Copy link
Contributor

ibond84 commented Sep 5, 2018

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

Почему вы не хотите убрать блокировку

Потому что программа упадет через 2 секунды при первом же проходе сборщика мусора.
Но pinned-блокировку я добавлю все равно.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants