需要一个高效算法模型

ilikeff8 2019-04-12 09:36:29
需求很简单,概况下来就是
1 上位机软件A控制一种硬件B
2 A通过协议连续发送命令给B
3 一次命令发送是堵塞式的,即A发送给B成功后,再等待B返回或超时后,再发送下一条命令,一个来回的时间是usedTime
4 A的发送速度不能大于B的CPU的承受能力,所以每一次发送前要延迟一个时间delayTime(1<=t<=30ms)
5 看起来3和4是矛盾的,但实际上B上可以挂很多的A,即B和A是一对多通道的关系,而每个通道里的命令是独立发送和接收的,但B的总体CPU承受能力是有限的,太频繁的多个A通道会赌赛B导致B内部排队

现在就要设计一个简单快速的数学模型(否则如果模型计算速度大多数下比delayTime还长就没意义了),根据以往的usedTime能智能动态调节这次发送命令前的delayTime,简单说就是返回变慢时减缓发送速率,而返回正常时加快发送速率

目前简单写了一个效果不好,意义不大,只是根据上一次的时间来判断:


void AdjustDelayTime(uint usedTime)
{
if (oldUsedTime == 0)
{
oldUsedTime = usedTime;
}
else
{
int result=usedTime.CompareTo(oldUsedTime * 2);

if (result < 0)
{
delayTime -= 3;
}
else if (result > 0)
{
delayTime += 3;
}

if (delayTime<3)
{
delayTime = 3;
}

if (delayTime>30)
{
delayTime = 30;
}

oldUsedTime = usedTime;
}
}


...全文
164 16 打赏 收藏 转发到动态 举报
写回复
用AI写文章
16 条回复
切换为时间正序
请发表友善的回复…
发表回复
wanghui0380 2019-04-12
  • 打赏
  • 举报
回复
我们不用sleep原因是,我们不需要想你考虑的说“如果sleep比他长”就没意义 因为就算我超时时间设置为1年,但是他100毫秒就回复了,那么我们还是立刻放行,而不是让他sleep 1年。 我们设置那个时间只有2目的,1是防止他死锁在那里,2才是如果rx观察源策略认为需要调整放行时间以减少超时回复次数
wanghui0380 2019-04-12
  • 打赏
  • 举报
回复
看到明白了吧,我不是管发送,在你发送哪里折腾一个sleep 我们管的是发,收一组。完成一组做下一组,如果超时了,放弃前一组,直接做下一组。 这就是基本逻辑,也是符合你的描述的问答式协议逻辑。那么这里唯一的就是调整那个放行时间 这块我个人喜欢rx,把收发时间丢到rx观察数据源里,后面订阅就可以设定调整策略了。比如我可以观察连续10次的情况,连续10次80%概率都超时,那么放行时间加倍,如果连续10次不超时,那么我可以把放行时间减半
wanghui0380 2019-04-12
  • 打赏
  • 举报
回复
Thread.Sleep(delayTime); 又看到这个了 唉。 20年前没办法才这样。 现在是一个 System.Threading.SemaphoreSlim 就好 System.Threading.SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); async Task 发送命令() { await _semaphore.WaitAsync(TimeSpan.FromMilliseconds(100)); //发送你的指令 await //异步收取指令, _semaphore.Release(); } 上下限都是1的异步信号锁, 也就是只能跑一个,多的自己排队。 整句话的意思是,发送指令一次只能进一个,如果在100毫秒以内收到回复,那么开门放下一个。如果超过100毫秒,不管收到没收到,都会开门放下一个。 那么,你唯一需要做的事情是根据统计情况调整,那个timespan
ilikeff8 2019-04-12
  • 打赏
  • 举报
回复
        void UpdateTagScreen_FeedBack(ReceiveData receiveData)
{
asyncConnectionInformationList.Where(p => p.ID == receiveData.id).First().AutoResetEvent.Set();
OnUpdateTagScreenAsyncFeedBack?.Invoke(receiveData.id, receiveData.data[1], receiveData.data[3] == 1);
}
ilikeff8 2019-04-12
  • 打赏
  • 举报
回复
引用 7 楼 以专业开发人员为伍 的回复:
其实遇到“要延迟一个时间”这个说法本身就知道遇到了一个极其垃圾的系统了。不管系统资源有多好或者多么差,但是在协议设计上也不需要什么“延迟时间”。一旦说什么 Sleep(或者说 Delay)那就是遇到一个比较垃圾的系统。

比如说 A 给 B 发送消息,那么就应该该干什么就干什么,不占用任何线程。过一会儿 B 给 A 发送消息,A 就会触发事件。处理了事件之后再执行下一个消息的发送,没有捕获事件则不会继续循环。

这是一个异步的、事件驱动的设计思路。如果只知道“阻塞”那就纠结了。


这没办法,因为B是一个CAN协议的硬件,然后转TCP过来,A可能有N个,而堵塞不是系统本身,而是设计规定,因为软件发送完命令必须要知道硬件是否已经完成然后通过socket返回成功信号才运行做下一步,这块无法异步,目前使用的是SocketAsyncEventArgs的完成端口


sendBytes[10] = numberBytes[1];
sendBytes[11] = 0;
sendBytes[12] = 0;

AsyncConnectionInformation asyncConnectionInformation = asyncConnectionInformationList.Where(p => p.ID == id).FirstOrDefault();

if (asyncConnectionInformation == null)
{
return false;
}

bool result = false;

try
{
uint startTime = timeGetTime(); // 高精度计时器
asyncConnectionInformation.UpdateTagTotalTimeList[interAddress - 1] = 0;

Thread.Sleep(delayTime);

result = Send(asyncConnectionInformation, sendBytes, timeOut);

if (result)
{
result = asyncConnectionInformation.AutoResetEvent.WaitOne(timeOut);

if (result)
{
asyncConnectionInformation.UpdateTagTotalTimeList[interAddress - 1] = timeGetTime() - startTime;
// AdjustDelayTime(asyncConnectionInformation.UpdateTagTotalTimeList[interAddress - 1]);
}
}
}
finally
{
timeEndPeriod(1);
}

return result;
  • 打赏
  • 举报
回复
其实遇到“要延迟一个时间”这个说法本身就知道遇到了一个极其垃圾的系统了。不管系统资源有多好或者多么差,但是在协议设计上也不需要什么“延迟时间”。一旦说什么 Sleep(或者说 Delay)那就是遇到一个比较垃圾的系统。 比如说 A 给 B 发送消息,那么就应该该干什么就干什么,不占用任何线程。过一会儿 B 给 A 发送消息,A 就会触发事件。处理了事件之后再执行下一个消息的发送,没有捕获事件则不会继续循环。 这是一个异步的、事件驱动的设计思路。如果只知道“阻塞”那就纠结了。
  • 打赏
  • 举报
回复
假设一个进程 C 来代理1万个A 进程,假设 C 内部各个代理以异步多线程方式来访问 B,那么其实就是
lock(sb)
{
    对B进行阻塞式地一问一答();
}
使得异步变为同步、多线程被卡成单线程,多垃圾的东西都能 lock 阻塞排队。
  • 打赏
  • 举报
回复
还啥模型?既然 B 这么蠢笨,那么直接就用一个独立地 C 来代理一堆 A 排队不就行了。
wanghui0380 2019-04-12
  • 打赏
  • 举报
回复
做复杂了,既然是问答式的,那么就是,他不回答不发,直到超时,重试。 所以没所谓调整什么策略。就算调整策略,其实也是简单的策略,比如指数退避 比如先等100毫秒,他响应不过来,给200毫秒,还是响应不过来给400毫秒,直到他能顺利响应。如果顺利响应并且能持续10个包,这从400降到200看能维持下去不,维持不下去再回400
ilikeff8 2019-04-12
  • 打赏
  • 举报
回复
简单说就是返回变慢时减缓发送速率,而返回正常时加快发送速率

的意思是

返回正常:一个实际运行的动态平衡值T,例如usedTime一直保持在一个合理区间里就认为是返回正常,负荷合理
合理区间:基本上就可以定义为不会越来越慢的一段测试区间
ilikeff8 2019-04-12
  • 打赏
  • 举报
回复
单通道返回usedTime测试大概为 25ms<t<60ms,但遇到网络波动突发时间有时会 >100ms,<1000ms(超时时间)
ilikeff8 2019-04-12
  • 打赏
  • 举报
回复
AdjustDelayTime效果不好的原因是没有忽略正常的返回波动,例如29ms->30ms->28->50ms->49ms.....正常波动应该不改动delayTime而应减少delayTime到一个固定值以提高发送效率,
但阈值又不能太高,例如usedTime大于oldUsedTime2倍时才调节,这种方法会让波动太大
wanghui0380 2019-04-12
  • 打赏
  • 举报
回复
你在13楼发的那个目的是池化,而非限流。那是为了让你别无限制的去new SocketAsyncEventArgs 当然为了大并发,通常这个池都比大。 所以他在限流上没啥作用,你现在需要做的不是说限制这个池的能力。你现在要做的事情,是让指令发送/指令接收 自然配对,然后对这组操作进行限流(因为他说了,不允许你大并发,大并发你能处理,他处理不来)
wanghui0380 2019-04-12
  • 打赏
  • 举报
回复
实际上你这个就是“限流”课题 限流常见的无非就是令牌桶,漏桶,加权降权处置 这些都是“自然”流处置,不是强制让他“睡觉”的处置 比如你一样可以用Semaphore 做个100,200的令牌桶,令牌用玩了,他自然就得排队等前面的超时走掉,自然他速度就降下来的,同样如果前面都不超时,都能很快吧令牌还回来,那么自然我也没必要限流,他能正常处理,限制个啥 ps:限流是限制并发,让他有规律的过去,不是让你睡过去,睡过去限制不了并发,因为大家一起睡的,也一起醒的,一起往前冲的
wanghui0380 2019-04-12
  • 打赏
  • 举报
回复
你还没想明白么,人家能正常不超时的回复你才是问题关键 不是你要睡到他回复你。 你发10000w个,他正常回复你10000w个,他能回复,你为他担心个啥 只有他大面积回复超时了,你才需要延长“锁门”的时间,减少发送频率 另外你自己说的你那个“waitone”,那你的设置成1,1才行?why?因为那是所谓的iocp池化的控制项,他保证的是连接并发,不是为你的问答预备的。 你sleep最大的问题也就是这个,假设你并发1000w个指令,然后他们一起sleep了,接着呢,等睡醒了,还是1000w一起并发发送,所以人家怪罪你短时间给了大量请求也是有原因的
ilikeff8 2019-04-12
  • 打赏
  • 举报
回复
引用 10 楼 wanghui0380 的回复:
Thread.Sleep(delayTime); 又看到这个了

唉。

20年前没办法才这样。

现在是一个 System.Threading.SemaphoreSlim 就好

System.Threading.SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);



async Task 发送命令()
{
await _semaphore.WaitAsync(TimeSpan.FromMilliseconds(100));
//发送你的指令
await //异步收取指令,
_semaphore.Release();
}

上下限都是1的异步信号锁, 也就是只能跑一个,多的自己排队。
整句话的意思是,发送指令一次只能进一个,如果在100毫秒以内收到回复,那么开门放下一个。如果超过100毫秒,不管收到没收到,都会开门放下一个。

那么,你唯一需要做的事情是根据统计情况调整,那个timespan




this.maxConnectNumber = maxConnectNumber;
this.receiveBufferSize = receiveBufferSize;

bufferManager = new BufferManager(receiveBufferSize * maxConnectNumber * 2, receiveBufferSize);
socketAsyncEventArgsStack = new SocketAsyncEventArgsStack(maxConnectNumber);
maxConnectCountSemaphore = new Semaphore(maxConnectNumber, maxConnectNumber);
asyncConnectionInformationList = new List<AsyncConnectionInformation>();

SocketAsyncEventArgs readWriteEventArg;

for (int i = 0; i < maxConnectNumber; i++)
{
readWriteEventArg = new SocketAsyncEventArgs();
readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
readWriteEventArg.UserToken = new AsyncConnectionInformation();

bufferManager.SetBuffer(readWriteEventArg);
socketAsyncEventArgsStack.Push(readWriteEventArg);
}



你说的是这个么,1个并发数SemaphoreSlim(1, 1)的效果和AutoResetEvent效果是一样的吧,都是等一个信号量,
实际上 result = asyncConnectionInformation.AutoResetEvent.WaitOne(timeOut); 才是堵塞等返回用的
而thread.sleep就是为了强制等待一段时间用的,因为asyncConnectionInformation.AutoResetEvent.Set是过快的,这是硬件决定的,
感觉没有太好的办法,是想复杂了

110,537

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧