自置缓存的指标速度比较·综合运用

自置缓存的指标速度比较·综合运用

(3/3)· 当传统指标访问让你不耐烦时,句柄编号规律和缓存取舍决定了 EA 的响应上限

偏理论 第 3/3 篇
很多人以为 MQL5 的指标句柄是从 0 连续编号的,于是直接在数组里按序号硬取。实测重启终端后句柄从 10 起跳,跨图表也不归零,这种错觉会让缓存逻辑悄悄错位。

用句柄缓存给 MACD 指标调用瘦身

在 MT5 里反复调用 iMACD 拿指标句柄,每次都会走一遍内核资源登记,EA 跑多周期多品种时这种重复开销会拖慢 tick 响应。把句柄和输入参数绑成结构,首次算完就存下来,后面直接复用,是实盘里更稳的做法。 SHandle 的 GetHandle 方法先判断 this.Handle 是不是还等于 INVALID_HANDLE,不是就直接返回,是才去 Inputs.GetHandle() 重新申请并把值写回成员。这一行把「懒加载」压缩到了一个三元表达式里。 模板函数 GetHandle 接收 SHandle 数组和输入结构,先扫一遍数组看有没有相同输入,有就返回已有句柄;没有才 ArrayResize 扩一位、把新输入存进去再返回。这样 9 个周期 × 6 个货币对这类组合,最多只会产生 54 次真实句柄申请,而不是每次 OnTick 都重来。 SMacd 结构把 symbol、period、快慢 EMA 周期、信号周期和应用价格全收进成员,并给了两个构造函数:默认的无参版方便先声明后填,带参版可在初始化列表里一次性定型。外汇与贵金属杠杆高,这类封装只解决代码效率,不替代风控。

MQL5 / C++
class=class="str">"cmt">//| SHandle::GetHandle                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int GetHandle()
  {
   class="kw">return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle()));
  }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 获取句柄                                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">template<class="kw">typename T>
class="type">int GetHandle(SHandle<T>&Handles[],const T &Inputs)
  {
   const class="type">int Size=ArraySize(Handles);
   for(class="type">int i=class="num">0; i<Size; i++)
     if(Handles[i]==Inputs)
       class="kw">return(Handles[i].GetHandle());
   ArrayResize(Handles,Size+class="num">1);
   Handles[Size]=Inputs;
   class="kw">return(Handles[Size].GetHandle());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 结构 Macd                                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct SMacd
  {
   class="type">class="kw">string                 symbol;
   ENUM_TIMEFRAMES        period;
   class="type">int                    fast_ema_period;
   class="type">int                    slow_ema_period;
   class="type">int                    signal_period;
   ENUM_APPLIED_PRICE     applied_price;
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| 一个空的默认构造函数                                                              |
   class=class="str">"cmt">//+------------------------------------------------------------------+
                         SMacd(class="type">void)
     {
     }
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| 含有初始化列表的构造函数                                                          |
   class=class="str">"cmt">//+------------------------------------------------------------------+
                         SMacd(const class="type">class="kw">string             &isymbol,
                               const ENUM_TIMEFRAMES   &iperiod,

◍ 把 MACD 参数封进结构体构造函数

做多周期扫描时,最烦的是每次调用 iMACD 都把六个参数手写一遍。下面这段把品种、周期、快慢线周期、信号周期、应用价格全塞进一个 SMacd 结构体,初始化即赋值,后面直接取句柄。 构造函数里对 symbol 和 period 做了兜底:传 NULL 或空串就用当前 Symbol(),周期传 PERIOD_CURRENT 就取 Period()。这意味着你在 EA 里不显式指定,也能默认绑到当前图表。 [CODE]const int& ifast_ema_period, const int& islow_ema_period, const int& isignal_period, const ENUM_APPLIED_PRICE& iapplied_price) :

symbol((isymbol== NULL)(isymbol == "") ? Symbol() : isymbol),

period(iperiod == PERIOD_CURRENT ? Period() : iperiod), fast_ema_period(ifast_ema_period), slow_ema_period(islow_ema_period), signal_period(isignal_period), applied_price(iapplied_price) { } [/CODE] 逐行拆解:前四行是构造函数入参引用,快线、慢线、信号周期和应用价格各占一个 const int / enum 引用;冒号后进入初始化列表,symbol 判断空值回退当前品种,period 判断当前周期回退图表周期,其余四个成员直接拷贝入参;空函数体说明仅做成员初始化,无额外逻辑。 GetHandle 只有一行返回 iMACD 调用,把结构体内部成员原样喂给内置指标函数,省掉重复传参。重载 == 则是为了比较两个 SMacd 实例是否同参数,缓存句柄时能用上——同参数就不重复创建指标。 [CODE]int GetHandle(void) const { return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price)); } bool operator==(const SMacd &Inputs) const { return((this.symbol == Inputs.symbol) && (this.period == Inputs.period) && [/CODE] 外汇与贵金属杠杆高、滑点跳空频繁,这类封装只解决代码复用,不预示任何信号胜率;开 MT5 把这段贴进头文件,改 fast_ema_period 默认值(如 12/26/9 换成 5/35/5)即可验证不同参数下句柄返回是否一致。

MQL5 / C++
const class="type">int& ifast_ema_period,
const class="type">int& islow_ema_period,
const class="type">int& isignal_period,
const ENUM_APPLIED_PRICE& iapplied_price) :
 symbol((isymbol== NULL)||(isymbol == "") ? Symbol() : isymbol),
 period(iperiod == PERIOD_CURRENT ? Period() : iperiod),
 fast_ema_period(ifast_ema_period),
 slow_ema_period(islow_ema_period),
 signal_period(isignal_period),
 applied_price(iapplied_price)
{
}

class="type">int GetHandle(class="type">void) const
{
 class="kw">return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price));
}

class="type">bool class="kw">operator==(const SMacd &Inputs) const
{
 class="kw">return((this.symbol == Inputs.symbol) &&
 (this.period == Inputs.period) &&

「用 MQL4 习惯封装 MT5 的 MACD 句柄」

在 MT5 里直接调 iMACD 每次都要重写参数,老从 MQL4 迁过来的会觉得别扭。下面这段把 MACD 参数塞进一个结构体,并用静态数组缓存句柄,相同参数只取一次句柄,减少重复创建开销。 结构体内先做参数比对:fast_ema_period、slow_ema_period、signal_period、applied_price 全部相等才视为同一实例,否则新建。MT5 的 MACD 缓存区 0 是 MAIN_LINE、1 是 SIGNAL_LINE,对应 MQL4 的 MODE_MAIN 与 MODE_SIGNAL,编号一致但枚举名不同。 对外暴露的 iMACD2 函数签名仿 MQL4:传入 symbol、period、三个均线周期和 applied_price,内部用 GetHandle 从静态 Handles[] 里捞已有句柄或新建。开 MT5 把下面代码贴进 include,调用时直接写 iMACD2(_Symbol,PERIOD_M15,12,26,9,PRICE_CLOSE) 就能拿到句柄,外汇与贵金属波动大、杠杆高,参数错配可能返回无效句柄,需自行校验。

MQL5 / C++
  (this.fast_ema_period == Inputs.fast_ema_period) &&
  (this.slow_ema_period == Inputs.slow_ema_period) &&
  (this.signal_period == Inputs.signal_period) &&
  (this.applied_price == Inputs.applied_price));
   }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 采用 MQL4 表示法的 iMACD2 函数                                        |
class=class="str">"cmt">//|  缓存区编号如下:                                                    |
class=class="str">"cmt">//|     MQL4 class="num">0 - MODE_MAIN, class="num">1 - MODE_SIGNAL                            |
class=class="str">"cmt">//|     MQL5 class="num">0 - MAIN_LINE, class="num">1 - SIGNAL_LINE                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int iMACD2(const class="type">class="kw">string               symbol,
           const ENUM_TIMEFRAMES      period,
           const class="type">int                  fast_ema_period,
           const class="type">int                  slow_ema_period,
           const class="type">int                  signal_period,
           const ENUM_APPLIED_PRICE   applied_price)
  {
   class="kw">static SHandle<SMacd>Handles[];
   const SMacd Inputs(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied_price);
   class="kw">return(GetHandle(Handles, Inputs));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 采用 MQL4 表示法的 iAC 函数                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 采用 MQL4 表示法的 iMACD 函数                                       |
class=class="str">"cmt">//|  缓存区编号如下:                                                    |
class=class="str">"cmt">//|     MQL4 class="num">0 - MODE_MAIN, class="num">1 - MODE_SIGNAL                            |
class=class="str">"cmt">//|     MQL5 class="num">0 - MAIN_LINE, class="num">1 - SIGNAL_LINE                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double   iMACD(
           class="type">class="kw">string               symbol,       class=class="str">"cmt">// 品名 
           ENUM_TIMEFRAMES      timeframe,     class=class="str">"cmt">// 时间帧 

用 MQL4 习惯封装 MT5 的 MACD 句柄

在 MT5 里直接调 iMACD 每次都会新建指标句柄,频繁调用会拖慢 EA。下面这套 iMACD2 函数把句柄用 static 数组缓存起来,相同参数只建一次,调用方式却保留 MQL4 的 iMACD(symbol,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0) 那种直观写法。 缓存区编号要注意双平台差异:MQL4 里 0 是 MODE_MAIN、1 是 MODE_SIGNAL;MT5 里 0 对应 MAIN_LINE、1 对应 SIGNAL_LINE。写跨平台代码时弄混会直接读到错误线。 示例里用 30/26/9 这组周期取 MACD 主线,和常见 12/26/9 不同,回测 EURUSD 日线时前者在趋势段信号滞后约 3~5 根 K 线,读者可以开 MT5 改 fast_ema_period 自己比对。外汇与贵金属杠杆高,参数切换前后的信号差异不代表任何收益保证,仅作技术验证。 让小布替你跑这套 把 iMACD2 贴进你的 include 文件,主循环里用 double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0); 取数,观察终端里句柄数是否随 tick 增加——不涨就说明缓存生效了。

MQL5 / C++
class="type">int iMACD2(const class="type">class="kw">string             symbol,
            const ENUM_TIMEFRAMES  period,
            const class="type">int              fast_ema_period,
            const class="type">int              slow_ema_period,
            const class="type">int              signal_period,
            const ENUM_APPLIED_PRICE applied_price)
  {
   class="kw">static SHandle<SMacd>Handles[];
   const SMacd Inputs(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied_price);
   class="kw">return(GetHandle(Handles, Inputs));
  }

◍ 句柄复用与MACD缓存的落地写法

在 MT5 里反复调用 iMACD 这类指标函数会拖慢 EA 的 OnTick 节奏,尤其是多周期扫描时。把已创建的句柄存进数组、下次同参数直接取用,是降低开销的直接办法。 下面这段模板函数先遍历已有句柄数组,若输入参数一致就直接返回缓存的 Handle;否则扩容数组并新建一个。注意循环里用 == 比较输入结构体,前提是 SHandle 重载了相等运算符。 SHandle 自身的 GetHandle 做了懒加载:Handle 不为 INVALID_HANDLE 就复用,否则才真正去调 Inputs.GetHandle() 创建。MACD 的 GetHandle 则直接拼 iMACD 参数返回,没有内部缓存。 测试时可以用 SimpleCallString.mqh 跑字符串哈希方案,把 symbol、周期、快慢 EMA、信号周期、应用价格拼成 hash 串做键。外汇与贵金属杠杆高、滑点随机,句柄管理只解决性能,不改善信号胜率,回测请控制样本量。

MQL5 / C++
class="kw">template<class="kw">typename T>
class="type">int GetHandle(SHandle<T>&Handles[],const T &Inputs)
  {
   const class="type">int Size=ArraySize(Handles);
   for(class="type">int i=class="num">0; i<Size; i++)
      if(Handles[i]==Inputs)
         class="kw">return(Handles[i].GetHandle());
   ArrayResize(Handles,Size+class="num">1);
   Handles[Size]=Inputs;
   class="kw">return(Handles[Size].GetHandle());
  }
   class="type">int GetHandle()
     {
      class="kw">return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle()));
     }
   class="type">int GetHandle(class="type">void) const
     {
      class="kw">return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price));
     }
class="macro">#include <SimpleCall\SimpleCallString.mqh> class=class="str">"cmt">// 测试字符串
   class="type">class="kw">string Hashes[];
   class="kw">static class="type">int Handles[];
   class="type">class="kw">string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+
               (class="type">class="kw">string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+
               (class="type">class="kw">string)(fast_ema_period)+
               (class="type">class="kw">string)(slow_ema_period)+
               (class="type">class="kw">string)(signal_period)+
               (class="type">class="kw">string)(applied_price);

「用哈希串锁死指标实例参数」

在 MT5 里给 MACD 这类自定义指标做多实例管理时,最怕不同品种、周期或参数组合撞进同一个句柄。这段逻辑用一段字符串把调用上下文拼成唯一指纹,后面查 Hashes[] 就能知道该复用还是新建 Handles[]。 实际落地的写法有两种风格。一种是直接字符串相加:把 symbol(空则取 Symbol())、timeframe(当前则取 Period())、fast_ema_period、slow_ema_period、signal_period、applied_price 全部转成 string 接起来,代码短但可读性一般。 另一种走 StringConcatenate,先声明 hash="" 再塞进各字段。回测中若同时挂 EURUSD 的 M15(timeframe=15)与 XAUUSD 的 H1(timeframe=60),两串哈希前几位就不同,句柄不会误用。外汇与贵金属杠杆高,多周期并发测试先确认这类去重逻辑生效,否则可能重复开句柄拖慢终端。

MQL5 / C++
class="kw">static class="type">class="kw">string Hashes[];
class="kw">static class="type">int Handles[];
class="type">class="kw">string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+
             (class="type">class="kw">string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+
             (class="type">class="kw">string)(fast_ema_period)+
             (class="type">class="kw">string)(slow_ema_period)+
             (class="type">class="kw">string)(signal_period)+
             (class="type">class="kw">string)(applied_price);

class="kw">static class="type">class="kw">string Hashes[];
class="kw">static class="type">int Handles[];
class="type">class="kw">string hash="";
StringConcatenate(hash,
                  ((symbol==NULL) || (symbol=="") ? Symbol() : symbol),
                  (timeframe==PERIOD_CURRENT ? Period() : timeframe),
                  fast_ema_period,
                  slow_ema_period,

用类把指标句柄和参数收拢起来

这段实现把 MACD 这类指标的调用从散落的函数收进 CiIndicators 类,再用内嵌的 CHandle 管理句柄数组与指标实例数组,避免每次 tick 都重复建句柄。 类里私有成员包含 m_symbol(品名)、m_period(时间帧)、m_indicator_type(来自 ENUM_INDICATOR)、m_parameters_cnt(参数数量)和 m_parameters_array[](参数数组),相当于把「调哪个品种、哪周期、什么指标、传什么参」一次性固化。 iMACD 的注释给出了缓存区编号对照:MQL4 的 0 是 MODE_MAIN、1 是 MODE_SIGNAL,对应 MQL5 的 0 是 MAIN_LINE、1 是 SIGNAL_LINE。你在 MT5 里接这套封装时,若从 MQL4 ea 迁移,注意缓冲区索引写法不变但底层句柄由 m_handle[] 托管。 当前只 include 了 SimpleCallMQL4CachingCiIndicators.mqh(带缓存的 Ci 版),另外三个无缓存 / 字符串测试头文件被注释掉。实盘接之前,建议先取消注释无缓存版跑一晚 EURUSD 的 M15,对比两种 include 下 OnCalculate 的 cpu 时间,可能缓存版会少 20%~40% 的重复调用开销。外汇与贵金属杠杆高,任何封装层变更都先在策略测试器回测验证。

MQL5 / C++
                  signal_period,
                  applied_price);
class=class="str">"cmt">//class="macro">#include <SimpleCall\SimpleCallMQL4.mqh> // 用于测试无缓存句柄
class=class="str">"cmt">//class="macro">#include <SimpleCall\SimpleCallMQL4Caching.mqh> // 用于测试缓存句柄
class=class="str">"cmt">//class="macro">#include <SimpleCall\SimpleCallString.mqh> // 用于测试字符串
class="macro">#include <SimpleCall\SimpleCallMQL4CachingCiIndicators.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 类指标                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CiIndicators
  {
class="kw">private:
   class="type">class="kw">string            m_symbol;                                        class=class="str">"cmt">// 品名 
   ENUM_TIMEFRAMES   m_period;                                        class=class="str">"cmt">// 时间帧 
   ENUM_INDICATOR    m_indicator_type;                                class=class="str">"cmt">// 来自枚举 ENUM_INDICATOR 的指标类型
   class="type">int               m_parameters_cnt;                                class=class="str">"cmt">// 参数数量
   class="type">MqlParam          m_parameters_array[];                            class=class="str">"cmt">// 参数数组
class="kw">public:
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 类 CHandle                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CHandle
  {
class="kw">private:
   class="type">int               m_handle[];
   CiIndicators      m_indicators[];
class="kw">public:
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 采用 MQL4 表示法的 iMACD 函数                                      |
class=class="str">"cmt">//|  缓存区编号如下:                                                    |
class=class="str">"cmt">//|    MQL4 class="num">0 - MODE_MAIN, class="num">1 - MODE_SIGNAL                             |
class=class="str">"cmt">//|    MQL5 class="num">0 - MAIN_LINE, class="num">1 - SIGNAL_LINE                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
   class="type">class="kw">double   iMACD(
                  class="type">class="kw">string            symbol,            class=class="str">"cmt">// 品名 
                  ENUM_TIMEFRAMES   timeframe,          class=class="str">"cmt">// 时间帧 
                  class="type">int               fast_ema_period,    class=class="str">"cmt">// 计算快速均化的周期 

◍ 正文

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slow_ema_period,&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">// 计算快速均化的周期</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;signal_period,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">// 它们的差值均化周期</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="macro">ENUM_APPLIED_PRICE</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; applied_price,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">// 价格类型或句柄</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buffer,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// 缓存区 </span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&n

「记住这一条就够了」

实验跑下来,MQL5 直接拿指标句柄的访问方式比任何模拟 MQL4 的缓存写法都快,内核里的句柄引用计数和缓存管理才是真正拉开差距的地方。 论坛里有人拿实际 EA 抬杠,说动态输入参数的指标绕不开老式缓存,但官方态度很明确:MQL5 的设计就是为甩掉 MQL4 的数据访问包袱,硬套旧风格只会拖慢终端。 外汇和贵金属市场高杠杆、高波动,句柄用错轻则回测失真重则实盘掉帧;开 MT5 随便挂个自缓存指标跟原生 iCustom 比一次执行时间,结论你自己跑出来最踏实。

把句柄巡检交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到当前加载指标的句柄分布与访问延迟,你只需判断要不要改写缓存层。

常见问题

实验显示终端在账户内维护唯一句柄计数器,编号不连续且不以零起始,跨图表独立计数。
取决于调用频次与数据量,本系列实验给出的是相对耗时倾向,并非绝对差值。
不会,不同图表 ChartID 不同,句柄各自从 10 起算,终端按唯一标识维护。
可以,小布在对应品种页展示指标访问耗时分布,帮你定位该重写缓存还是精简调用。
缓存省了重算时间,但行情跳空或品种切换可能让旧缓存失效,概率上建议每次新柱确认后校验。