当前位置: 首页 > Web前端 > HTML

前端开发中React调度算法的迭代过程

时间:2023-03-28 13:08:49 HTML

React最难理解的部分就是“调度算法”,不仅抽象复杂,而且还要重构一次。可以说只有React团队才能完全理解这个算法。在这种情况下,本文试图从React团队成员的角度来谈谈“调度算法”。什么是调度算法?React在v16之前面临的主要性能问题是:当组件树非常大时,更新状态可能会导致页面卡顿。根本原因在于更新过程是“同步且不可中断”的。为了解决这个问题,React提出了Fiber架构,意在“让更新过程异步且可中断”。最终的交互过程如下:不同的交互产生不同优先级的更新(比如onClick回调中的更新优先级最高,useEffect回调中触发的更新优先级平均)。“调度算法”从众多更新中选择一个优先级作为本次渲染的优先级,即渲染步骤2中选择的优先级的组件树。在渲染过程中,如果再次触发交互过程,则更高的优先级为在步骤2中选择,之前的渲染被打断,从新的开始前端训练。优先重启渲染。本文要讲的是第2步中的“调度算法”。expirationTime调度算法“调度算法”中需要解决的最基本的问题是:如何选择其中一个更新的优先级作为这个渲染的优先级?最早的算法称为expirationTime算法。具体更新优先级与“当前触发交互的时间”和“优先级对应的延迟时间”有关://MAX_SIGNED_31_BIT_INT最大为31位Intergerupdate.expirationTime=MAX_SIGNED_31_BIT_INT-(currentTime+updatePriority);例如,高优先级高优先级更新u1和低优先级更新u2的updatePriority分别为0和200,则MAX_SIGNED_31_BIT_INT-(currentTime+0)>MAX_SIGNED_31_BIT_INT-(currentTime+200)//即u1.expirationTime>u2。过期时间;意味着u1具有更高的优先级。expirationTime算法的原理简单易懂:每次都选择所有更新中“优先级最高”的那个。如何表示“batch”此外,还有一个问题需要解决:如何表示“batch”?什么是“批次”?考虑以下示例://定义状态numconst[num,updateNum]=useState(0);//...某处修改num//修改方式1updateNum(3);//修改方式2updateNum(num=>num+1);两种“修改状态的方法”都会创建更新,不同的是:第一种方法不需要考虑更新前的状态,直接修改状态num为3第二种方法需要根据“更新”Previousstate”来计算新状态由于第二种方法的存在,更新之间可能存在连续性。因此,“调度算法”计算出一个优先级后,实际参与计算“当前状态值”的是什么时候组件渲染的是:“计算出的优先级的对应更新”+“与该优先级相关的其他优先级的对应更新”这些相互关联的、顺序的更新被称为一个“批次”。expirationTime算法计算“批次”的方式也很简单,crude:优先级大于某个值(priorityOfBatch)的更新会被归为同一个batchconstisUpdateIncludedInBatch=priorityOfUpdate>=priorityOfBatch;expirationTime算法确保渲染是异步可中断的,并且始终首先处理具有最高优先级的更新。此功能在此期间称为异步模式。IO密集场景下的AsyncMode可以解决以下问题:组件树逻辑复杂导致更新时卡顿(因为组件渲染变得可中断),重要交互响应更快(因为不同交互产生不同优先级的更新).这些问题统称为CPU密集型问题。在前端,还有一类问题也会影响体验,那就是“请求数据导致的等待”。此类问题称为IO绑定问题。为了解决IO密集型问题,React提出了Suspense。考虑以下代码:constApp=()=>{const[count,setCount]=useState(0);useEffect(()=>{constt=setInterval(()=>{setCount(count=>count+1);},1000);return()=>clearInterval(t);},[]);return(<>loading...

}>
countis{count}
/>);};其中:每秒会触发一次更新,状态count会更新为count=>count+1,在Sub中会发起一个异步请求。在请求返回之前,包裹在Sub中的Suspense将渲染fallback。假设三秒后请求返回,理想情况下,请求前后的UI会依次显示://Sub中的请求发起前我是sub,count为0
countis0
//RequestinSub发起第一秒
loading...
countis1
//Sub中的request在第二秒发起
loading...
countis2
//Sub中的请求在第三秒发起
loading...
countis3
//Sub中请求成功后我是sub,请求成功,countis4
countis4
计数为4
计数为4
div>从用户的角度来看,有两个任务在并发执行:请求Sub的任务(观察第一个div的变化)和改变计数的任务(观察第二个div的变化)。Suspense带来了“多任务并发”,因此AsyncMode(异步模式)也改名为ConcurrentMode(并发模式)。一个无法解决的bug,那么Suspense对应更新的优先级是高还是低呢?当请求成功时,合理的逻辑应该是“尽快展示成功的UI”,所以Suspense对应的更新应该是高优先级的更新,那么例子中有两种更新:Suspense对应的高质量IO更新,参考为u0,每秒产生低质量的CPU更新,称为u1、u2、u3等。在expirationTime算法下://u0的优先级远高于u1、u2、u3...u0.expirationTime>>u1.expirationTime>u2.expirationTime>...u0的优先级最高,所以u1和后续更新需要等待u0执行完毕,而u0需要等待“requestcompleted”再执行。因此发起请求前后,UI会依次显示为://Sub中请求发起前我是sub,count为0
countis0
//Sub在请求发起的第1秒
loading...
countis0
//Sub在请求发起的第1秒second
loading...
countis0
//第3秒发起Sub中的请求
loading...
countis0
//Sub中请求成功后我是sub,请求成功,count为4
count为4
从用户的角度来看,第二个div是卡住3秒然后突然变4。因此,当只考虑CPU密集型场景时,“先执行高优先级更新”的算法是没有问题的。但是,考虑到IO密集型场景,高质量的IO更新会阻塞低质量的CPU更新,这显然是错误的。所以expirationTime算法不能很好地支持并发更新。bug产生原因expirationTime算法最大的问题是expirationTime字段耦合了“优先级”和“批次”两个概念,限制了模型的表达能力。这会导致高质量IO更新不会与低质量CPU更新分到同一“批次”中。那么低质量的CPU更新必须要等到高质量的IO更新处理完毕后才能处理。如果不同的更新可以根据实际情况灵活分“批”,就不会出现这个bug。重构迫在眉睫,重构的目标很明确:拆分“优先级”和“批次”两个字段。车道调度算法新的调度算法称为车道。他如何定义“优先级”和“批次”?对于优先级,一个lane是一个32bit的Integer,最高位是符号位,所以最多可以有31位参与运算。不同优先级对应不同lane,越低的位代表越高的优先级,比如://对应SyncLane,为最高优先级0b0000000000000000000000000000001//对应InputContinuousLane0b0000000000000000000000000000100//对应DefaultLane0b0000000000000000000000000010000//对应IdleLane0b0100000000000000000000000000000//对应OffscreenLane,为最低优先Level0b1000000000000000000000000000000"batch"isdefinedbylanes,andalaneisalsoa32bitInteger,representing"acollectionofoneormorelanes".Multiplelanescanbeeasilyassignedtothesamebatchwithbitoperations://BatchtobeusedletlanesForBatch=0;constlaneA=0b0000000000000000000000001000000;constlaneB=0b000000000000000000000000000001;//IncludelanesForBatchinthebatch|=laneA;//IncludelaneBinthebatchlanesForBatch|=laneB;TheSuspensebugmentionedaboveiscausedbytheexpirationTimealgorithmnotbeingabletoflexiblydefinebatches.Laneshasnosuchconcernsatall,andanypriority(lane)thatwantstobedesignatedasthesame"batch"canbeeasilydonewithbitoperations.Tosumup,the"schedulingalgorithm"needstosolvetwoproblems:theexpirationTimefieldusedinthepriorityselectionbatchexpirationTimealgorithmcouplesthesetwoconcepts,resultingininflexibility.Thesourceofthearticleissofun