Overclocking .NET

    .Net are used for programming desktop and web applications, and is it possible to use the framework for managing industrial facilities?
    Let us examine at the beginning where it is possible to use such software.
    Industrial control systems consist of several levels:
    • sensor level
    • control level (PLC, computer)
    • visualization level / SCADA

    Visualization is the most unpretentious ACS level in terms of information processing speed, usually the operator panels display information about the state of an aggregate of the entire system (some numerical values, status bits (on-off), graphs of changes in values). For this level, you can freely use all the features of .NET to display graphical information (Win Forms, WPF, SilverLight).
    Consider the level of management. Here, the required information processing speed depends on the control object: if you just want to blink an LED or turn on / off the engine without a rigid time reference, then this is possible. If it is necessary to control objects with a strictly defined reaction time, then it is necessary to use a real-time system.
    Real-time systems require a real-time OS. In such OSs, the time for switching the context of tasks, i.e. the time slice for processing each stream with the same priority is allocated the same.
    And what can happen when using a “desktop” OS?

    I want to consider what speed can be achieved using Windows and .Net. In this case, there will be obstacles for high speed:
    • parallel-running OS processes
    • background services
    • .NET application garbage collector
    • checks when executing managed code

    We set the task: there is a PC with Windows XP + USB-COM adapter based on the FTDI controller. With what speed is it possible to switch the RTS output of the adapter to obtain a stable meander (without changing the pulse repetition period)? Such a task is the simplest option for managing external devices with a program on a PC.
    To approximate the performance of the program to the real-time mode, we use increasing the priority of the program and the work flow.

    A bit of theory on the Windows Task Scheduler



    There are 32 priority levels in total:
    • 0 system level "zero page thread"
    • 1-15 adjustable levels
    • 16-31 real time levels

    In the range of changing priorities, classes are additionally distinguished:
    • Idle (2-6)
    • Below Normal (4-8)
    • Normal (6-10)
    • Above Normal (8-12)
    • High (11-15)
    • Real-time (22-26)

    Each process, when creating (CreateProcess ()), can be assigned a priority class; if it is not specified, the process receives a priority class of Normal (level 8). Each process thread can be individually assigned a priority level. The total, basic, priority of the stream is calculated by the combination of the class of the priority of the process and the level of priority of the stream. If the basic priority of a thread belongs to a group of real-time priorities (level 16-31), then it does not change dynamically, otherwise the scheduler may slightly change the priority of threads depending on the current processor load and events (UI, I / O).
    The quantum of time for each thread to work is 10ms for a system with one processor and 15ms if the system is multiprocessor. Also, the quantum depends on the type of OS (regular or server (“long quanta” are used in the server)), as well as on the state of the application (the priority of UI threads and threads working with input / output devices temporarily increases when the corresponding events occur).
    For our example, we use the “toughest” option - set the process priority class in Real-time and the priority of the workflow in Highest. The base priority level of the thread in this combination is 24. At this priority level, the scheduler should not switch the context to other threads (because besides system processes, there are no other threads with this priority).

    Program


    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.IO.Ports;
    using System.Threading;
    using System.Windows.Forms;
    using FTD2XX_NET;

    private bool _exitThread;
    private Thread _workThreadPort, _workThreadPortUnmanaged, _workThreadDriver;
    private List _durationsPort, _durationsPortUnmanaged, _durationsDriver;

    public FormMain()
    {
        InitializeComponent();
        //System.Diagnostics.Process.GetCurrentProcess().PriorityClass =
        // ProcessPriorityClass.RealTime;
        // поток для теста через класс SerialPort
        _workThreadPort=new Thread(()=>
                                        {
                                            SerialPort port = new SerialPort("COM5");
                                            port.BaudRate = 115200;
                                            port.StopBits = StopBits.One; 
                                            port.Parity = Parity.None;
                                            port.DataBits = 7;
                                            port.Handshake = Handshake.None;
                                            port.Open();
                                            bool flag=false;
                                            Stopwatch stopwatch = new Stopwatch();
                                            stopwatch.Start();
                                            //Thread.CurrentThread.Priority = ThreadPriority.Highest;
                                            while (!_exitThread)
                                            {
    _durationsPort.Add(stopwatch.ElapsedMilliseconds);
                                                stopwatch.Reset();
                                                stopwatch.Start();
                                                port.RtsEnable = flag;
                                                flag = !flag;
                                            }
                                            port.Close();
                                        });
        // поток для теста через неуправляемый код (Win API)
        _workThreadPortUnmanaged = new Thread(() =>
                                            {
                                            UnmanagedSerialPort unmanagedSerialPort = new UnmanagedSerialPort("COM5");
                                            unmanagedSerialPort.Open();
                                            bool flag=false;
                                            Stopwatch stopwatch = new Stopwatch();
                                            stopwatch.Start();
                                            //Thread.CurrentThread.Priority = ThreadPriority.Highest;
                                            while (!_exitThread)
                                            {
                                                _durationsPortUnmanaged.Add(stopwatch.ElapsedMilliseconds);
                                                stopwatch.Reset();
                                                stopwatch.Start();
                                                if (flag)
                                                {
                                                    unmanagedSerialPort.On();
                                                }
                                                else
                                                {
                                                    unmanagedSerialPort.Off();
                                                }
                                                flag = !flag;
                                            }
                                            unmanagedSerialPort.Close();
                                            });
        // поток для теста через API производителя чипа FTDI
        _workThreadDriver=new Thread(()=>
                                            {
                                            FTDI myFtdiDevice = new FTDI();
                                            myFtdiDevice.OpenByIndex(0);
                                            bool flag=false;
                                            Stopwatch stopwatch = new Stopwatch();
                                            stopwatch.Start();
                                            //Thread.CurrentThread.Priority = ThreadPriority.Highest;
                                            while (!_exitThread)
                                            {
                                                FTDI.FT_STATUS ftStatus = myFtdiDevice.SetRTS(flag);
                                                if (ftStatus == FTDI.FT_STATUS.FT_OK)
                                                {
                                                    flag = !flag;
                                                    _durationsDriver.Add(stopwatch.ElapsedMilliseconds);
                                                    stopwatch.Reset();
                                                    stopwatch.Start();
                                                }
                                            }
                                            myFtdiDevice.Close();
                                            });
        _durationsDriver = new List();
        _durationsPort = new List();
        _durationsPortUnmanaged = new List();
    }

    // сохранение статистики
    private void b_Save_Click(object sender, EventArgs e)
    {
        StreamWriter sw = new StreamWriter("Port.csv");
        _durationsPort.ForEach(l => sw.WriteLine(l));
        sw.Close();
        sw = new StreamWriter("Driver.csv");
        _durationsDriver.ForEach(l => sw.WriteLine(l));
        sw.Close();
        sw = new StreamWriter("PortUnmanaged.csv");
        _durationsPortUnmanaged.ForEach(l => sw.WriteLine(l));
        sw.Close();
    }

    private void b_RunViaPort_Click(object sender, EventArgs e)
    {
        _exitThread = false;
        _workThreadPort.Start();
    }

    private void b_Stop_Click(object sender, EventArgs e)
    {
        _exitThread = true;
    }

    private void b_RunViaPortUnmanaged_Click(object sender, EventArgs e)
    {
        _exitThread = false;
        _workThreadPortUnmanaged.Start();
    }

    private void b_RunViaDriver_Click(object sender, EventArgs e)
    {
        _exitThread = false;
        _workThreadDriver.Start();
    }

    * This source code was highlighted with Source Code Highlighter.


    Consider 3 options for working with the port:
    • class SerialPort
    • unmanaged code (Win API: GetCommState, EscapeCommFunction, SetCommState)
    • library supplied by FTDI

    In each case, we change the state of the RTS pin in the program cycle, recording the time of one cycle. This time corresponds to the time when the pin switches to the desired state.
    Test conditions: each of the options is checked for 30 s, after 10 s the processor is loaded with an additional application for 10 s (WinRar - hardware performance test). Each option is checked with the usual (level 8) priority of the stream (blue graph) and Real-time (level 24, red graph).

    results


    SerialPort class
    image
    Unmanaged code and FTDI library
    image
    The graphs show that when working through the standard SerialPort class, the cycle time is about 5 times longer than when working through a "non-standard" solution. Increasing the priority of the stream reduces the time of one cycle to 2ms, such a stream is not interrupted.

    conclusions


    When managing external devices from a PC using a managed .net code, the reaction time will be no less than 1-2ms. It is possible to partially reduce the influence of parallel processes by increasing the priority of the flow. At the same time, one should not forget about other processes and, if possible, manually switch the context (Thread.Sleep (0)) to other waiting threads. Avoid unnecessary garbage collection (GC) calls by rational work with objects, use the correct application architecture, this can be traced by the profiler or system performance counters. Also in a multiprocessor system, you can assign different threads to different processors (see SetThreadAffinityMask ()).
    The purpose of the article is not to make another bike, it is clear that you can’t do without a PLC or microcontroller to control technological processes; I want to show that for .NET it is possible to find application in solving a certain range of problems, where the required minimum reaction time of the system to the impact is more than 2-15ms.
    I would like to see .NET RT in the next N years, following the example of the Java Real-Time System. As well as the industrial application of the .NET Micro Framework on PLCs of well-known companies (Siemens, Omron).

    Update1

    References


    C # for Real-time - an article that prompted me on this topic
    Requirements for a Real-Time .NET Framework - this article describes the principles necessary for implementing .NET RT based on a comparison with Java Real-time
    Real-time GC in Java - Thanks to conscell
    . Comparison of Thread.Sleep (0) and Thread.Sleep (1): here and here - Thanks Frozik and tangro

    Also popular now: