Автор Тема: Трансляция Oberon в C#  (Прочитано 9622 раз)

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Трансляция Oberon в C#
« : Апрель 16, 2012, 01:00:57 pm »
Обдумываю как могла бы выглядеть правильная трансляция Оберона в C#.

Трансляция используемая в GPCP абсолютно неправильная ибо в GPCP оберонистые value-типы эмулируются дотнетными ссылочными, что убивает производительность на корню.

С точки зрения C# язык Oberon ужасно-ужасно-ужасно ансэйфный.

Вот, например, простенький модуль на Обероне:

MODULE Test;

  TYPE
    Message* = RECORD END;

    MouseMsg* = RECORD (Message)
      x*, y*: INTEGER
    END;

    KeyboardMsg* = RECORD (Message)
      ch*: CHAR
    END;

    Controller* = RECORD
      handler*: PROCEDURE (VAR controller: Controller; VAR message: Message);
    END;

    MyController = RECORD (Controller)
      ...
    END;

  PROCEDURE MyHandler (VAR controller: Controller; VAR message: Message)
  BEGIN
    IF message IS MouseMsg THEN
      ...
    ELSIF message IS KeyboardMsg THEN
      ...
    END
  END

  PROCEDURE Smth (VAR c: Controller)
    VAR mouseMsg: MouseMsg; keyboardMsg: KeyboardMsg;
  BEGIN
    mouseMsg.x := 10;
    mouseMsg.y := 20;
    c.handler(c, mouseMsg);
    keyboardMsg.c := 'A';
    c.handler(c, keyboardMsg)
  END Smth;

  PROCEDURE Do* ()
    VAR c: MyController;
  BEGIN
    c.handler := MyHandler;
    Smth(c)
  END Do;

END Test.


Транслируем его в C#.

Сначала вспомогательный модуль на C#:

public static unsafe class SYSTEM
{
   public struct TYPE
   {
      public TYPE* ext0;
      public TYPE* ext1;
      public TYPE* ext3;
      public TYPE* ext4;
      public TYPE* ext5;
      public TYPE* ext6;
      public TYPE* ext7;
   }

   public struct ANYREC
   {
      public TYPE* type;
   }

   public static System.IntPtr ALLOC (int size)
   {
      return System.Runtime.InteropServices.Marshal.AllocHGlobal(size);
   }

   public static void FREE (void* pointer)
   {
      System.Runtime.InteropServices.Marshal.FreeHGlobal((System.IntPtr)pointer);
   }

   public static System.Delegate DELEGATE (System.IntPtr p, System.Type t)
   {
      return System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer(p, t);
   }

   public static System.IntPtr PROCEDURE (System.Delegate d)
   {
      return System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(d);
   }
}


А теперь модуль Test:

public static unsafe class Test
{
   public struct Message // : SYSTEM.ANYREC
   {
      public SYSTEM.TYPE* type;

      public static readonly SYSTEM.TYPE* Type;

      static Message ()
      {
         Type = (SYSTEM.TYPE*)SYSTEM.ALLOC(sizeof(SYSTEM.TYPE));
         Type->ext0 = Type;
      }
   }

   public struct MouseMsg // : Message
   {
      public SYSTEM.TYPE* type;
      public int x;
      public int y;

      public static readonly SYSTEM.TYPE* Type;

      static MouseMsg ()
      {
         Type = (SYSTEM.TYPE*)SYSTEM.ALLOC(sizeof(SYSTEM.TYPE));
         Type->ext0 = Message.Type;
         Type->ext1 = Type;
      }
   }

   public struct KeyboardMsg // : Message
   {
      public SYSTEM.TYPE* type;
      public char ch;

      public static readonly SYSTEM.TYPE* Type;

      static KeyboardMsg ()
      {
         Type = (SYSTEM.TYPE*)SYSTEM.ALLOC(sizeof(SYSTEM.TYPE));
         Type->ext0 = Message.Type;
         Type->ext1 = Type;
      }
   }

   public struct Controller // : SYSTEM.ANYREC
   {
      public SYSTEM.TYPE* type;
      public System.IntPtr handler;

      public static readonly SYSTEM.TYPE* Type;

      static Controller ()
      {
         Type = (SYSTEM.TYPE*)SYSTEM.ALLOC(sizeof(SYSTEM.TYPE));
         Type->ext0 = Type;
      }
   }

   public delegate void Handler (Controller* controller, Message* message);

   public struct MyController // : Controller
   {
      public SYSTEM.TYPE* type;
      public System.IntPtr handler;

      public static readonly SYSTEM.TYPE* Type;

      static MyController ()
      {
         Type = (SYSTEM.TYPE*)SYSTEM.ALLOC(sizeof(SYSTEM.TYPE));
         Type->ext0 = Controller.Type;
         Type->ext1 = Type;
      }
   }

   public static Handler myControllerHandler = new Handler(MyHandler);

   public static void MyHandler (Controller* controller, Message* message)
   {
      if (message->type->ext1 == MouseMsg.Type)
      {
         MouseMsg* m = (MouseMsg*)message;
         System.Console.WriteLine("MyController.Handler() Receive MouseMsg x={0} y={1}", m->x, m->y);
      }
      else if (message->type->ext1 == KeyboardMsg.Type)
      {
         KeyboardMsg* m = (KeyboardMsg*)message;
         System.Console.WriteLine("MyController.Handler() Receive KeyboardMsg ch={0}", m->ch);
      }
   }

   public static void Do ()
   {
      MyController c;
      c.type = MyController.Type;
      c.handler = SYSTEM.PROCEDURE(myControllerHandler);
      Smth((Controller*)&c);
   }

   private static void Smth (Controller* c)
   {
      MouseMsg mouseMsg;
      mouseMsg.type = MouseMsg.Type;
      mouseMsg.x = 10;
      mouseMsg.y = 20;
      ((Handler)SYSTEM.DELEGATE(c->handler, typeof(Handler)))(c, (Message*)&mouseMsg);

      KeyboardMsg keyboardMsg;
      keyboardMsg.type = KeyboardMsg.Type;
      keyboardMsg.ch = 'A';
      ((Handler)SYSTEM.DELEGATE(c->handler, typeof(Handler)))(c, (Message*)&keyboardMsg);
   }
}


По производительности реализация на C# будет медленнее из-за того что в  C# вместо указателей на процедуры используются делегаты (которые суть динамические объекты). Тормозить будет конвертация из указателя в делегат.

Лучше будет, видимо, вовсе не использовать GetDelegateForFunctionPointer/GetFunctionPointerForDelegate, а класть делегаты в глобально доступный статический массив и вместо указателя на функцию использовать индекс в этом массиве делегатов. Проигрыш по производительности всё равно будет, но не таким большим как при постоянном мусорении объектами делегатов.


valexey

  • Administrator
  • Hero Member
  • *****
  • Сообщений: 1990
    • Просмотр профиля
Re: Трансляция Oberon в C#
« Ответ #1 : Апрель 16, 2012, 01:44:19 pm »
Вообще, если нам не нужно отдавать наши объектики в другой .net-код, то их вполне можно располагать в собственном стеке, который с точки хрения .net'а будет просто обычным массивом байт.
"но сейчас, чтобы компенсировать растущую мощность компьютеров, программисты используют фреймворки"

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Трансляция Oberon в C#
« Ответ #2 : Апрель 16, 2012, 02:02:48 pm »
В размещении объектов проблемы нет. Разумеется вместо System.Runtime.InteropServices.Marshal.AllocHGlobal / FreeHGlobal можно и даже нужно написать свой аллокатор на массивах байтов (а лучше на массивах UInt64  :) )

Проблема есть с процедурными переменными. В ансэйфный код (в массив байтов) делегат засунуть нельзя ибо он managed object. Преобразовывать делегат в указатели System.IntPtr и обратно накладно. Остаётся класть их в глобальный managed массив и обращаться по целочисленному индексу.  Это увеличивает накладные расходы на одно косвенное обращение. Фактически этот оверхед почти такой же как пенальти для виртуальных функций, то есть в принципе жить можно.

Peter Almazov

  • Sr. Member
  • ****
  • Сообщений: 482
    • Просмотр профиля
Re: Трансляция Oberon в C#
« Ответ #3 : Апрель 16, 2012, 02:15:23 pm »
Обдумываю как могла бы выглядеть правильная трансляция Оберона в C#.
А почему в C#? Тогда уж в IL.

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Трансляция Oberon в C#
« Ответ #4 : Апрель 16, 2012, 02:40:09 pm »
А почему в C#? Тогда уж в IL.
Они практически 1:1.

Для понимания пишем текст на C#, а при реальной кодогенерации будет сразу IL минуя C#.

valexey

  • Administrator
  • Hero Member
  • *****
  • Сообщений: 1990
    • Просмотр профиля
Re: Трансляция Oberon в C#
« Ответ #5 : Апрель 16, 2012, 02:41:27 pm »
Но вообще, это конечно уже оптимизация. То есть она может быть прозрачно сделана уже после "наивной" реализации, которая будет стабильна безглючна и функционально полна.

Кстати, при таких вот оптимизациях возникнут проблемы с интеропом с .net кодом (стандартные и не очень .net либы) - для закидывания в них наших объектиков на стеке нужно будет их вначале "сконвертировать" во что-то более традиционное .net'овое. Что приведет к дополнительному оверхеду. Соответственно если код тесно работает с .net-либами внешними, то возможно такая вот оптимизация выйдет боком и код будет работать медленнее. То есть оптимизатор должен быть достаточно умным чтобы не оптимизировать там, где это может привести к тормозам. Хотя-я... Если мы хотим передать что-то во внешнюю либу, можно не на стеке это дело размещать, а в куче через NEW - оптимизатор туда кривыми ручками не сунется.

PS. А для каких целей думаешь Оберон.net использовать? И какой его диалект?
"но сейчас, чтобы компенсировать растущую мощность компьютеров, программисты используют фреймворки"

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Трансляция Oberon в C#
« Ответ #6 : Апрель 16, 2012, 02:46:22 pm »
Вариант с вызовом процедур по индексу из глобального managed массива соответствующих делегатов:

public static unsafe class Test
{
   ...

   public struct Controller // : SYSTEM.ANYREC
   {
      public SYSTEM.TYPE* type;
      public uint handler; // индекс в массиве Handler.procedures (ссылается на Handler.Procedure)

      public static readonly SYSTEM.TYPE* Type;

      static Controller ()
      {
         Type = (SYSTEM.TYPE*)SYSTEM.ALLOC(sizeof(SYSTEM.TYPE));
         Type->ext0 = Type;
      }
   }

   public static class Handler
   {
      public delegate void Procedure (Controller* controller, Message* message);

      public static Procedure[] procedures;
      private static uint n;

      static Handler ()
      {
         procedures = new Procedure[32];
         n = 1; // индекс 0 зарезервировали для NULL
      }

      public static uint Register (Procedure x)
      {
         lock (typeof(Handler))
         {
            if (n >= procedures.Length)
            {
               Procedure[] a = new Procedure[procedures.Length * 2];
               System.Array.Copy(procedures, a, procedures.Length);
               procedures = a;
            }
            procedures[n] = x;
            return n++;
         }
      }
   }


   public struct MyController // : Controller
   {
      public SYSTEM.TYPE* type;
      public uint handler; // индекс в массиве Handler.procedures (ссылается на Handler.Procedure)

      public static readonly SYSTEM.TYPE* Type;

      static MyController ()
      {
         Type = (SYSTEM.TYPE*)SYSTEM.ALLOC(sizeof(SYSTEM.TYPE));
         Type->ext0 = Controller.Type;
         Type->ext1 = Type;
      }
   }

   public static void MyHandler (Controller* controller, Message* message)
   {
      if (message->type->ext1 == MouseMsg.Type)
      {
         MouseMsg* m = (MouseMsg*)message;
         System.Console.WriteLine("MyController.Handler() Receive MouseMsg x={0} y={1}", m->x, m->y);
      }
      else if (message->type->ext1 == KeyboardMsg.Type)
      {
         KeyboardMsg* m = (KeyboardMsg*)message;
         System.Console.WriteLine("MyController.Handler() Receive KeyboardMsg ch='{0}'", m->ch);
      }
   }

   private static readonly uint myControllerHandler = Handler.Register(MyHandler);

   public static void Do ()
   {
      MyController c;
      c.type = MyController.Type;
      c.handler = myControllerHandler;
      Smth((Controller*)&c);
   }

   private static void Smth (Controller* c)
   {
      MouseMsg mouseMsg;
      mouseMsg.type = MouseMsg.Type;
      mouseMsg.x = 10;
      mouseMsg.y = 20;
      Handler.procedures[c->handler](c, (Message*)&mouseMsg);

      KeyboardMsg keyboardMsg;
      keyboardMsg.type = KeyboardMsg.Type;
      keyboardMsg.ch = 'A';
      Handler.procedures[c->handler](c, (Message*)&keyboardMsg);
   }
}



Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Трансляция Oberon в C#
« Ответ #7 : Апрель 16, 2012, 03:02:04 pm »
PS. А для каких целей думаешь Оберон.net использовать? И какой его диалект?
Собираюсь плясать от Oberon-07.

Собираюсь использовать для программ с большим количеством объектов (100 миллионов) и большим объёмом памяти (16-32 Гб).

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

valexey

  • Administrator
  • Hero Member
  • *****
  • Сообщений: 1990
    • Просмотр профиля
Re: Трансляция Oberon в C#
« Ответ #8 : Апрель 16, 2012, 03:06:05 pm »
PS. А для каких целей думаешь Оберон.net использовать? И какой его диалект?
Собираюсь плясать от Oberon-07.

Собираюсь использовать для программ с большим количеством объектов (100 миллионов) и большим объёмом памяти (16-32 Гб).

Сборщик мусора при таком количестве объектов делает слишком большие паузы в работе программы, поэтому хочу сделать его использование совершенно необязательным. Но тогда надо дать программистам хотя бы минимальную поддержку для "умных указателей", чтобы управление объектами было не полностью ручным, а как-то "автоматизированным".
Ага. То есть тебе нужен эдакий DSL для этой самой конкретной задачи (точнее класса задач), и по "счастливой случайности" Оберон по сути является языком который очень похож на этот DSL. А конкретно - хоть в нем и нет умных указателей, но зато есть VAR-аргументы функций которые позволяют сделать ровно то, что тебе нужно.

Оберон-07 как стартовая точка - правильное решение (IMHO).
"но сейчас, чтобы компенсировать растущую мощность компьютеров, программисты используют фреймворки"

Romiras

  • Sr. Member
  • ****
  • Сообщений: 264
    • Просмотр профиля
    • Romiras Dev Lab
Re: Трансляция Oberon в C#
« Ответ #9 : Апрель 16, 2012, 06:37:57 pm »
Сергей, эту трансляцию следует понимать, что эмуляция Оберона системными выкрутасами Си-диезом производительнее реализации Си-диеза? Я не понял суть трансляции и какое место занимает в ней Оберон.

Romiras

  • Sr. Member
  • ****
  • Сообщений: 264
    • Просмотр профиля
    • Romiras Dev Lab
Re: Трансляция Oberon в C#
« Ответ #10 : Апрель 16, 2012, 06:50:17 pm »
И еще мне трудно представить каким образом будут использоваться доступ ко всем этим объектам, занимающим 16ГБ или даже больше. Ведь такой объем памяти одним махом не обработать. Вместо того, чтобы  хранить все в памяти, дешевле хранить на диске и считывать при необходимости. В добавок, NoSQL вполне может помочь для такого случая.

valexey

  • Administrator
  • Hero Member
  • *****
  • Сообщений: 1990
    • Просмотр профиля
Re: Трансляция Oberon в C#
« Ответ #11 : Апрель 16, 2012, 07:01:18 pm »
И еще мне трудно представить каким образом будут использоваться доступ ко всем этим объектам, занимающим 16ГБ или даже больше. Ведь такой объем памяти одним махом не обработать. Вместо того, чтобы  хранить все в памяти, дешевле хранить на диске и считывать при необходимости. В добавок, NoSQL вполне может помочь для такого случая.
Многие БД (в том числе NoSQL, по моему MongoDB среди них) работают эффективно только если полностью помещаются в ОЗУ. А на диск они периодически flush'аются.

Так вот, у Сергея, насколько я понимаю, задача такова, что 16 Гб ОЗУ как раз есть (тем более что это не такой уж и огромный объем - стоит в общем то копейки, а относительно стоимости конечного продукта, оно стоит ровно нуль) а также имеются требование софтреалтайма к ПО, так что в таких условиях связываться с диском себе дороже будет (у дисков свои проблемы - они ограничены по io-операциям за единицу времени, они имеют дурную привычку выходить из строя, деградировать и так далее).

В общем можно считать что задача из разряда жирного (о-очень жирного) embedded. Что в микроконтроллерах старяются "диски" не использовать, что тут. Резоны одни и те же.
"но сейчас, чтобы компенсировать растущую мощность компьютеров, программисты используют фреймворки"

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Трансляция Oberon в C#
« Ответ #12 : Апрель 16, 2012, 09:48:37 pm »
Я не понял суть трансляции и какое место занимает в ней Оберон.
В дотнете есть Си-шные плоские структуры struct. Они не поддерживают наследование. Однако наследование структур можно организовать врукопашную.

Пример:

public struct R0
{
  public int x0;
}

public struct R1
{
  public int x0;
  public int x1;
}

Структуру R1 можно считать расширением структуры R0.

Указатели R0* и R1* приводятся друг к другу в unsafe режиме языка C#.

Фактически приведение типа R1* к типу R0* всегда совершенно безопасно, только C# компилятор об этом не знает и считает его ансэйфным.

Наоборот, приведение типа R0* к типу R1* опасное и должно предваряться динамической проверкой. Сверхскоростной алгоритм этой проверки я взял из Оберона.

И еще мне трудно представить каким образом будут использоваться доступ ко всем этим объектам, занимающим 16ГБ или даже больше. Ведь такой объем памяти одним махом не обработать.
Сейчас в персональные компьютеры можно втыкать 32 Гб ОЗУ, а в относительно недорогие рабочие станции и серверы -- сотни гигабайт.

На работе мы пишем телефонную станцию. Для обслуживания большого количества абонентов классический подход диктует делать систему масштабируемой на много серверов. Когда у оператора связи увеличивается количество абонентов, то он покупает ещё несколько серверов, система на них масштабируется и всё работает дальше. Это как бы верно, но сейчас компьютеры стали гораздо мощнее чем прежде. Тот компонент системы, разработкой которого занимаюсь я, при определённой дисциплине программирования фактически сможет обслуживать не 50-100 тысяч абонентов, а миллионы. Зачем покупать двадцать серверов если будет достаточно одного? Правда, начальство пока со мной по этому вопросу не согласно, мол купить ещё несколько серверов дешевле чем написать суперскую программу. Но я-то понимаю, что её написать могу. И не так уж это и сложно. При определённых ограничениях 1 миллион абонентов моя программа уже "держит".

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Трансляция Oberon в C#
« Ответ #13 : Апрель 17, 2012, 09:18:47 am »
Есть ещё другой способ. Более эффективный по памяти, но чуть медленнее по вызовам. Вместо хранения внутри записи восьмибайтового указателя на дескриптор типа можно хранить четырёхбайтовый индекс в массиве классов. Дескриптор типа теперь можно оформить как дотнетный class и получить со стороны дотнета полноценную поддержку виртуальных функций сделав использование делегатов необязательным.

public static class SYSTEM
{
   public struct ANYREC
   {
      public uint classId;
   }

   public class CLASS
   {
      public uint Id;
      public CLASS class0;
      public CLASS class1;
      public CLASS class2;
      public CLASS class3;
   }

   public static CLASS[] classes;
   private static uint classCount;

   static SYSTEM ()
   {
      classes = new CLASS[1024];
      classCount = 1;
   }

   public static void Register (CLASS c)
   {
      lock (typeof(SYSTEM))
      {
         if (classCount == classes.Length)
         {
            CLASS[] x = new CLASS[classCount * 2];
            System.Array.Copy(classes, x, classCount);
            classes = x;
         }
         c.Id = classCount++;
         classes[c.Id] = c;
      }
   }
}


public static class Test
{
   public struct Message // : SYSTEM.ANYREC
   {
      public uint classId;

      public static readonly SYSTEM.CLASS Class;

      static Message ()
      {
         Class = new SYSTEM.CLASS();
         Class.class0 = Class;
         SYSTEM.Register(Class);
      }
   }

   public struct MouseMsg // : Message
   {
      public uint classId;
      public int x;
      public int y;

      public static readonly SYSTEM.CLASS Class;

      static MouseMsg ()
      {
         Class = new SYSTEM.CLASS();
         Class.class0 = Message.Class;
         Class.class1 = Class;
         SYSTEM.Register(Class);
      }
   }

   public struct KeyboardMsg // : Message
   {
      public uint classId;
      public char ch;

      public static readonly SYSTEM.CLASS Class;

      static KeyboardMsg ()
      {
         Class = new SYSTEM.CLASS();
         Class.class0 = Message.Class;
         Class.class1 = Class;
         SYSTEM.Register(Class);
      }
   }

   public struct Controller // : SYSTEM.ANYREC
   {
      public uint classId;

      public class CLASS: SYSTEM.CLASS
      {
         public virtual unsafe void ProcessMessage (Controller* self, Message* message)
         {
         }
      }


      public static readonly CLASS Class;

      static Controller ()
      {
         Class = new CLASS();
         Class.class0 = Class;
         SYSTEM.Register(Class);
      }
   }

   public struct MyController // : Controller
   {
      public uint classId;

      public class CLASS: Controller.CLASS
      {
         public override unsafe void ProcessMessage (Controller* self, Message* message)
         {
            if (SYSTEM.classes[message->classId].class1 == MouseMsg.Class)
            {
               MouseMsg* m = (MouseMsg*)message;
               System.Console.WriteLine("MyController.Handler() Receive MouseMsg x={0} y={1}", m->x, m->y);
            }
            else if (SYSTEM.classes[message->classId].class1 == KeyboardMsg.Class)
            {
               KeyboardMsg* m = (KeyboardMsg*)message;
               System.Console.WriteLine("MyController.Handler() Receive KeyboardMsg ch='{0}'", m->ch);
            }
         }
      }


      public static readonly CLASS Class;

      static MyController ()
      {
         Class = new CLASS();
         Class.class0 = Controller.Class;
         Class.class1 = Class;
         SYSTEM.Register(Class);
      }
   }

   public static unsafe void Do ()
   {
      MyController c;
      c.classId = MyController.Class.Id;
      Smth((Controller*)&c);
   }

   private static unsafe void Smth (Controller* c)
   {
      MouseMsg mouseMsg;
      mouseMsg.classId = MouseMsg.Class.Id;
      mouseMsg.x = 10;
      mouseMsg.y = 20;
      ((Controller.CLASS)SYSTEM.classes[c->classId]).ProcessMessage(c, (Message*)&mouseMsg);

      KeyboardMsg keyboardMsg;
      keyboardMsg.classId = KeyboardMsg.Class.Id;
      keyboardMsg.ch = 'A';
      ((Controller.CLASS)SYSTEM.classes[c->classId]).ProcessMessage(c, (Message*)&keyboardMsg);
   }
}

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Трансляция Oberon в C#
« Ответ #14 : Апрель 17, 2012, 04:22:43 pm »