Voracious Socket.Poll

Greetings!
Please comment on the behavior of Stream.Poll. There is a very heavy load when calling the method. Specifically, when called with the microseconds = -1 parameter, the project performance drops by about 5 times for each created socket!
Those. 1 socket ~ 5 times, 2 sockets ~ 10 times, 3 sockets ~ 15 times, etc.

consider using a thread for each socket.

Now I do it. However, either performance or response time will suffer. Depending on the Socket.Poll / Thread.Sleep ratio. This is critical in both cases.
Using Socket.Poll, I expected to see the same behavior as Thread.Sleep with the release of the context. However, it behaves similarly to Thread.SpinWait and holds the current context. We have to idly “twist” the current context.
There was no such problem in .NETMF 4.3.

Wait connect:
instead:

var socket = this.ListenerSocket.Accept();

have to do:

while (!this.ListenerSocket.Poll(10, SelectMode.SelectRead))
{
    Thread.Sleep(10);
}
var socket = this.ListenerSocket.Accept();

Receive data:
instead

this._socket.Poll(-1, SelectMode.SelectRead);

have to do

while (!this._socket.Poll(10, SelectMode.SelectRead))
{
    Thread.Sleep(1);
}
1 Like

We will take a look, give more information as much as you can.

No connections

One TCP connection

Two TCP connection

1 Like

And if use poll(10) in loop with sleep then is it better or same?

Now I use for receive data:

while (!this._disposed)
{
	while (!this._socket.Poll(10, SelectMode.SelectRead))
	{
		Thread.Sleep(1);
	}
	//this._socket.Poll(-1, SelectMode.SelectRead);
	var available = this._socket.Available;
	if (available == 0) break;
	var buffer = new byte[available];
	var count = this._socket.Receive(buffer, SocketFlags.None);
	if (count == 0) break;
	OnRecevedEvent(buffer, 0, count);
}

And get 21 FPS on 1 connection.
Video for this code in previous post.

If I’m use this code:

while (!this._disposed)
{
	//while (!this._socket.Poll(10, SelectMode.SelectRead))
	//{
	//	Thread.Sleep(1);
	//}
	this._socket.Poll(-1, SelectMode.SelectRead);
	var available = this._socket.Available;
	if (available == 0) break;
	var buffer = new byte[available];
	var count = this._socket.Receive(buffer, SocketFlags.None);
	if (count == 0) break;
	OnRecevedEvent(buffer, 0, count);
}

Framerate down to 8 FPS on 1 connection.

Play around with the _testLoopCount, _socketsCount, _socketPollWait_us, _socketPollSleep_ms parameters.

using System;
using System.Diagnostics;
using System.Net.Sockets;
using System.Threading;
using GHIElectronics.TinyCLR.Devices.Network;
using GHIElectronics.TinyCLR.Pins;
namespace TinyCLR.Devices.Test
{
    public static class TestSockets
    {
        private static int _testLoopCount = 100;
        private static int _socketsCount = 0;
        private static int _socketPollWait_us = -1;
        private static int _socketPollSleep_ms = 10;
        
        public static void Test()
        {
            EthernetInit();
            new Thread(() => TestLoop(_testLoopCount)).Start();
            AddSockets(_socketsCount, _socketPollWait_us, _socketPollSleep_ms);
        }
        private static void TestLoop(int count)
        {
            double last = 0;
            double value;
            while (true)
            {
                value = 0;
                for (var i = 0; i < count; i++)
                {
                    value += DateTime.Now.TimeOfDay.TotalMilliseconds - last;
                    last = DateTime.Now.TimeOfDay.TotalMilliseconds;
                    Thread.Sleep(0);
                }
                Debug.WriteLine($"loop time {value / count:F3} ms");
            }
        }

        private static void EthernetInit()
        {
            var networkController = NetworkController.FromName(SC20260.NetworkController.EthernetEmac);

            var networkInterfaceSetting = new EthernetNetworkInterfaceSettings
            {
                MacAddress = new byte[] { 0x00, 0x04, 0x00, 0x00, 0x00, 0x00 },
                IsDhcpEnabled = true,
                IsDynamicDnsEnabled = true
            };

            networkController.SetInterfaceSettings(networkInterfaceSetting);
            networkController.SetAsDefaultController();

            networkController.Enable();
        }
        private static void AddSockets(int count, int pollWait, int pollSleep)
        {
            while (count-- > 0)
            {
                new Thread(() => StartSocket(pollWait, pollSleep)).Start();
            }
        }
        private static void StartSocket(int pollWait, int pollSleep)
        {
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
            while (!socket.Poll(pollWait, SelectMode.SelectRead))
            {
                Thread.Sleep(pollSleep);
            }
        }
    }
}
1 Like

prior to reading from the socket you are allocating an input buffer.

it is possible, with many sockets, that you are getting impacted by GC.

You might want to try preallocating the input buffer.

Further, have you calculated your expected transmission rate and compared it to the maximum rate of the board?

Yes, @Mike, I understand and take that into account. However, there is no physical connection in the test case I gave. And it is not tied in this case to physical interfaces and their processing speeds. Only create and idle socket polling without connecting or receiving data. For example, in server mode, while waiting for a client connection, Socket.Poll (-1, SelectMode.SelectRead) is executed inside Socket.Accept () and again we get the maximum time consumption.
And instead of laconic

var socket = this.ListenerSocket.Accept();

you have to make a similar crutch:

while (!this.ListenerSocket.Poll(10, SelectMode.SelectRead))
{
    Thread.Sleep(10);
}
var socket = this.ListenerSocket.Accept();

it smoothes out the RMS time absorption, but the peak does not go away and the friezes are visible on the GUI.

On a G120, I have a project that runs 6 TCP and 2 UDP servers at the same time. Accordingly, each of the TCP servers adds new sockets when the client connects. But on the G120, there is no such problem with soaking up idle time while waiting.

I ran the above test in different modes:

Sockets Poll Time Sleep Time, ms Loop Time, ms
0 - - 0,035
1 Inf - 55,98
1 10 us 0 26,48
1 100 us 0 26,48
1 1 ms 0 26,48
1 10 ms 0 26,48
1 100 ms 0 40,98
1 1 s 0 53,48
1 10 us 1 2,532
1 100 us 1 2,532
1 1 ms 1 2,532
1 10 ms 1 2,532
1 100 ms 1 7,532
1 1 s 1 31,024
1 10 us 10 0,528
1 100 us 10 0,535
1 1 ms 10 0,528
1 10 ms 10 0,528
1 100 ms 10 1,535
1 1 s 10 10,527
2 Inf - 111,979
2 10 us 0 52,98
2 100 us 0 52,98
2 1 ms 0 52,98
2 10 ms 0 52,98
2 100 ms 0 71,979
2 1 s 0 101,979
2 10 us 1 55,979
2 100 us 1 5,032 (float)
2 1 ms 1 5,032 (float)
2 10 ms 1 5,032 (float)
2 100 ms 1 10,31 (float)
2 1 s 1 102,979
2 10 us 10 55,979
2 100 us 10 1,028 (float)
2 1 ms 10 1,028 (float)
2 10 ms 10 1,028 (float)
2 100 ms 10 2,034 (float)
2 1 s 10 11,032

Interestingly, when the number of sockets is more than one for a time from 100 μs to 100 ms, the time is floating. I have launched more sockets, but the times in this range are no longer very predictable.

Can anyone confirm or deny my observations?

Confirm and fixed.

If you wanted a try, you can take a look:

The fixed is also in native. But this change in C# also make a different.

1 Like

I am glad to hear it! Looking forward to 2.1.0.