ABAP性能优化总结

时间:2024-02-24 13:06:10

前言

程序的效率是每个程序员开发者都应该重视的,无论您是采用哪一种语言进行开发. 程序有时候越短,并不一定越快,有时候程序很多代码,但不一定会很慢. 性能是一把双刃剑, 获得时间效率的同时, 牺牲的是空间的开销. 这里提供一些建议以提高你的程序运行速度和减低系统荷载。ABAP开发语言是SAP专属的应用层语言,它的语法与底层的数据库语言还是有些区别的,因为它本质是以内存消耗的一种应用语言,与“空间换时间”的理念很符合,为此ABAP对应的SQL称之为 OPEN SQL,本人的性能优化点主要是从OPEN SQL 与ABAP语法两方面进行展开。

一、核心优化点

1、筛选多值时,IN 的效率比LIKE更快

2、不用NOT IN,可以考虑先用IN,抽出记录后,再进行filter

3、用IN不用BETWEEN

4、尽可能不Full Table Scan,而进行Index Range Scan

5、不要在DB层排序,在内表排

不建议:

select * from VBAK INTO TABLE itb WHERE vbeln IN itb_vbeln ORDER BY PRIMARY KEY.

select * from VBAK INTO TABLE itb WHERE vbeln IN itb_vbeln ORDER BY erdate DESCENDING.

建议:

SORT itb BY erdat DESCENDING.

6、减少传输记录

SELECT * INTO TABLE

SELECT field list  INTO CORRESPONDING FIELDS OF TABLE itab

SELECT field list(具体指定) INTO TABLE... #即不需要的,根本不要列在这里

7、用WHERE,不用CHECK

8、避免用LOOP,而用FOR ALL ENTRIES IN,不过要判断内表不为空且删除重复记录

9、在DB层面进行汇总而不要在ABAP(OPEN SQL)层面

SELECT vbeln max(zmeng) min (matnr)
FROM vvbap INTO TABLE g_itab_vvbap
WHERE vbeln IN g_vbeln
GROUP BY vbeln.

10、GROUP BY后用HAVING而不用CHECK

11、高效数据读取方法

不用SELECT ...ENDSELECT方式,用SELECT... INTO TABLE..方式(ARRAY FETCH)

 说明:第二种只对 oracle 下一次命令 把数据抓回来放到 array 里面,

第一种一笔一笔的数据抓回来 放进去 internal table。

12、读取TOP N数据集

不建议用select ... endselect,建议用select ? up to n rows.

二、OPEN SQL优化点


1、抽取数据时,避免使用SELECT *, 尽量使用SELECT A B INTO TABLE ITAB这样的语句。
2、不要使用SELECT...ENDSELECT语句。
3、避免频繁使用SELECT SINGLE语句, 特别是在LOOP和SELECT...ENDSELECT里面用, 应该把要读取的数据用SELECT FOR ALL ENTRIES IN 一次全部取得, 然后用READ TABLE WITH KEY ... BINARY SEARCH.
4、用SORT代替ORDER BY
5、避免使用嵌套的循环
6、尽量不要使用JOIN/INNDER JOIN 进行多表连接。不要超过3个表的连接。把一个表的数据先取到内表,然后使用FOR ALL ENTRIES语句再进行抽取。
7、使用二分查找法。
   READ TABLE的之前使用SORT TABLE BY对内表进行排序, 然后使用READ TABLE WITH KEY BINARY SEARCH.
8、避免使用SELECT DISTINCT语句。在抽取数据到内表后用DELETE ADJACENT DUPLICATES语句来消除重复行。
9、尽量加多WHERE语句进行条件抽取。
以上,说的还不全,会进行不断更新。


另外,可以通过TCODE:ST05 SE30 进行程序和SQL语句性能和效率的分析

三、ABAP 语法优化点


首先是尽量减少I/O操作,类似对硬盘的读写的I/O操作是最耗费时间的, 比如读写数据库。以下是减少I/O操作的例子:

1, 减少数据库DB的读写操作, 当使用VIEW视图的时候, 当被视图join的table有数据更新操作的时候, 同时系统也会更新到这个view里面, 使得它们之间的数据一样, 所以使用视图会对这些日常操作带来效率问题. 如果视图join的表多数是日常事物需要更新的事物数据表(如EKET), 就要避免使用视图.

2, 避免使用SELECT *, 尽量使用SELECT A B C INTO TABLE ITAB这样的语句。这个操作会将所有符合条件的数据一次性地读进内表,这比在SELECT A B C INTO WA... APPEND... ENDSELECT的循环中添加数据到内表要快。不用频繁的读DB.

3, 避免频繁使用SELECT SINGLE语句, 特别是在LOOP和SELECT...ENDSELECT里面用, 应该把要读取的数据用SELECT FOR ALL ENTRIES IN 一次全部取得, 然后用READ TABLE WITH KEY ... BINARY SEARCH.

虽然说操作内存比磁盘操作要高效,但是如果对内存的使用不加以控制,可能有些时候不得不对硬盘的交换空间操作, 这样就增加了对磁盘的I/O读写操作.正如下面所说:

4, 当你定义内表的时候可以也会出现这样的问题, 比如你定义一个内表使用的是OCCURS 100,而不是OCCURS 0, 会导致内表长度大于100的时候,就会占用系统页面缓存。

5, Field-groups对于多层次的排序和显示是非常有用的。它是将数据写入系统的页面文件,而不是内存(内表一般是使用内存的)。基于这个原因,field-groups比较适合于处理大量数据的列表(一般超过50000条记录)。如果涉及大量的数据处理,应该首先和系统管理员协商来决定这个程序最多能使用多少内存,以计算这个程序需要使用多少资源。然后你就可以决定是把数据写入内存还是交换空间。

6, 用SORT代替ORDER BY, ORDER BY从句是执行在数据库服务器上, 而SORT是ABAP语句执行在应用服务器上的. 数据库服务器通常会形成性能瓶颈问题, 所以最好是吧数据导入内表做SORT.

7, 避免使用SELECT DISTINCT语句, 因为当用来判断唯一的字段为非索引字段时, 效率是十分的低, 内表SORT后, 使用DELETE ADJACENT DUPLICATES 来去重复.

其次就是要减轻CPU的负载, 可以通过优化程序来改善,

以下是减低CPU负载的优化例子:

1, 使用宏代替频繁函数调用. ABAP没有内联函数这个说法, 所以我们如果需要频繁调用函数时, 函数调用有栈内存创建和释放的开销. 在ABAP中可以用宏代码提高执行效率,宏代码不是函数但使用起来像函数,编译器用复制宏代码的方式取代函数调用,省去了参数压栈、从而提高速度。

注意使用宏有缺点:

(1)容易出错, 宏不能pass-by-value按值传递,用于代替实现函数功能时要十分注意!

(2)不可调试;

(3)无法操作类的私有数据成员.

2, 避免使用过得的LOOP 和SELECT .... END SELECT. 避免使用嵌套的LOOP 和SELECT .... END SELECT.


3, 尽可能多地使用表的KEY FIELD作为Where分句的条件选项。比如SELECT * FROM BSEG WHERE BUKRS = '1000' AND BELNR = '0100000007' AND GJAHR = '2006' AND BUZEI = '003'. 这里的四个字段BUKRS,BELNR,GJAHR,BUZEI 都是BSEG表的KEY字段.

4, 如果某些数据需要频繁的从不同表提取, 使用视图VIEW实现读取缓存可以提高效率. 当视图连接的是读取次数较多, 但写入不频繁的表时(比如物料主数据表MARA), 可以使用视图, 这样比在程序里面简单用join要快,理论上join语句每次读取的速度都是一样的, 而视图是从读二次开始就快了,而且cache使得网络负载减低

5, 使用SQL语句里面的JOIN时候, 应该避免JOIN的表不要超过3个, 否则严重影响效率.

如果真的要JOIN表3个以上的话, 正确的方法不是用视图VIEW, 而是使用SELECT ... INTO TABLE ... FOR ALL ENTRIES IN 以及 READ TABLE WITH KEY BINARY SEARCH.**

例如我们要提高读取BSEG表的性能,首先我们会根据GJAHR主键从BKPF表取出部分数据到内表itab,然后使用FOR ALL ENTRIES IN itab WHERE BSEGBELNR = itabBELNR 这样的的方法取得符合itab里所有条件的BSEG数据.**

注意:   使用FOR ALL ENTRIES要先CHECK作为条件的内表itab是否为空,还有作为WHERE的条件在这个内表里面是否会有空值存在,否则无效.
      要判断For all entries in后面的内表是否为空,如果它为空的话,那么在where条件中的与内表中字段进行比较的结果全部为真,也就是全部满足条件,这会导致取出非常多的数据,极大地影响系统的性能。**
6, 注意使用CORRESPONDING FIELDS OF 和  MOVE-CORRESPONDING 时候会进行字段比较, 带来CPU的开销大.
7, 避免过得而频繁的数据类型转换,比如N转换为C,但是从N转换成STRING却是很快的,因为操作STRING比CHAR类型少了比较长度的时间.
8, 使用二级索引提高DDIC的读写效率, 可以根据你的需要在SE11里面创建INDEX, 并让你程序里的SQL**查询语句里WHERE条件的顺序与你的索引顺序一致!!  注意喽
9, 二分查找比线性查找要高效,READ TABLE的之前使用SORT TABLE BY XXX 某个表关键字段进行排序(注意:要升序排列!), 然后使用READ TABLE WITH KEY XXX = 'XXX' BINARY SEARCH. 这个就是所谓的二分查找法的应用. 
10, 避免使用SQL语句动态查询条件,动态表名和动态字段名, 必要时候用宏或者子程序模块代替.

**   事实证明动态SQL查询效率很慢!!**

11, 对于同一功能的函数和方法, 调用METHOD比调用FUNCTION要快.

12, SORTED TABLE可以使用二分查找法取得节点, 其时间复杂度是O(log N),但是插入节点会慢,因为要移动很多节点. 而HASHED TABLE则是基于哈希算法的,其高效主要体现在把数据的存储和查找时间大大降低,几乎可以看成是常数时间O(1),而代价是消耗比较多的内存,然而在硬件技术越来越发达的今天,用空间换时间的做法在某种意义上是值得的。但是使用哈希表必须注意键值的唯一性!如果键值会出现重复的话,不能使用哈希表,只能用排序表和标准表。

13, 使用APPEND LINES(或者INSERT LINES) OF ITAB1 TO ITAB2 比

**    LOOP AT ITAB1 INTO WA.   **

**     APPEND(INSERT) WA TO ITAB2.**

**    ENDLOOP. 要高效.
**
14, 使用效率比较高的COLLECT, DELETE ADJACENT DUPLICATES FROM语句

15, 使用高效的CONTEXT SQL语句.如以下代码2比代码1要快10倍以上!

代码1:
 

  SELECT * FROM SBOOK INTO SBOOK_WA UP TO 10 ROWS.
  SELECT SINGLE AIRPFROM AIRPTO INTO (AP1, AP2)
         FROM  SPFLI
         WHERE CARRID = SBOOK_WA-CARRID
         AND   CONNID = SBOOK_WA-CONNID.
  SELECT SINGLE NAME INTO NAME1 FROM  SAIRPORT
         WHERE ID = AP1.
  SELECT SINGLE NAME INTO NAME2 FROM  SAIRPORT
         WHERE ID = AP2.
ENDSELECT.



代码2:  

SELECT * FROM SBOOK INTO SBOOK_WA UP TO 10 ROWS.
  SUPPLY CARRID = SBOOK_WA-CARRID
         CONNID = SBOOK_WA-CONNID
             TO CONTEXT TRAV1.
  DEMAND AIRPFROM   = AP1
         AIRPTO     = AP2
         NAME_FROM  = NAME1
         NAME_TO    = NAME2
             FROM CONTEXT TRAV1.
ENDSELECT.


最后是注意内存的使用,以下是内存优化方面的例子:

1, 虽然ABAP拥有垃圾处理的机制, 但是这个是在程序运行完成后实现的. 所以我们尽量把无用的变量,内表,对象都释放掉;

2, 尽量减少无用的静态定义的变量,内表和对象, 因为静态定义的对象会在编译开始就占有内存空间;

3, 尽量减少网络的传输负载, 比如在设计RFC远程调用传回的内表数据要尽量精简, 数据量越大,对CPU和内存需求越多;

4, 内存使用紧张的情况下, 使用FREE语句, 以及SQL语句的PACKAGE SIZE n 选项.

SELECT vbeln erdat
FROM vbak
INTO TABLE li_vbak PACKAGE SIZE 50.

1、使用where语句  代替CHECK语句
不推荐
Select* from zflight.
Check : zflight-airln = ‘LF’ and zflight-fligh = ‘BW222’.
Endselect.
推荐
Select* from zflight where airln = ‘LF’ and fligh = ‘222’.
Endselect.

2、使用聚合函数
不推荐
Maxnu = 0.
Select* from zflight where airln = ‘LF’ and cntry = ‘IN’.
Check zflight-fligh >maxnu.
Maxnu = zflight-fligh.
Endselect.
推荐
Select max( fligh ) from zflight intomaxnu where airln = ‘LF’ and cntry = ‘IN’.

3、使用视图代替基本表查询   多用下
不推荐
Select* from zcntry where cntry like ‘IN%’.
Selectsingle * from zflight where cntry = zcntry-cntry and airln = ‘LF’.
Endselect.
推荐
Select* from zcnfl where cntry like ‘IN%’ and airln = ‘LF’.
Endselect.

4、使用INTO table 代替select endselect
不推荐
Refresh: int_fligh.
Select* from zflight into int_fligh.
Append int_fligh. Clear int_fligh.
Endselect.
推荐
Refresh: int_fligh.
Select* from zflight into table int_fligh.

5、使用批量修改内表代替逐行修改
不推荐
Loop at int_fligh.
If int_fligh-flag is initial.
Int_fligh-flag = ‘X’.
Endif.
Modify int_fligh.
Endloop.
推荐
Int_fligh-flag = ‘X’.
Modify int_fligh transporting flag where flag is initial.
当使用不带表头的内表时,需要从工作区删除,例如
MODIFY IT_TEST1 FROM WA_TEST1 TRANSPORTING BOX WHERE BOX IS INITIAL.

6、使用二分法查询,提高查询内表数据速度
不推荐
Read table int_fligh with key airln = ‘LF’.
推荐
Read table int_fligh with key airln = ‘LF’ binary search.

7、两个内表添加使用批量增加代替逐行
不推荐
Loop at int_fligh1.
Append int_fligh1 to int_fligh2.
Endloop.
推荐
Append lines of int_fligh1 to int_fligh2.

8、使用table buffering


Use of buffered tables is recommended to improve the performance considerably. The buffer is bypassed while using the following statementsSelectdistinct
Select… for update
Order by, group by, having clause
Joins
Use the Bypass buffer addition to theselectclause in order to explicitly bypass the buffer whileselecting the data.

9、 使用FOR ALL Entries
不推荐
Loop at int_cntry.

   Select single * from zfligh into int_flighwhere cntry = int_cntry-cntry.

   Append int_fligh.

Endloop.
推荐
Select * from zfligh

appending table int_fligh
For all entries in int_cntry
Where cntry = int_cntry-cntry.

10、正确地使用where语句,使查询能使用索引When a base table has multiple indices, the where clause should be in the order of the index, either a primary or a secondary index
To choose an index, the optimizer checks the field names specified in the where clause and then uses an index that has the same order of the fields. One more tip is that if a table begins with MANDT, while an index does not, there is a high possibility that the optimizer might not use that index.

11、正确地使用MOVE语句
Instead of using the move-corresponding clause it is advisable to use the move statement instead. Attempt should be made to move entire internal table headers in a single shot, rather than moving the fields one by one.

12、正确地使用inner joinLet us take an example of 2 tables, zairln and zflight. The table zairln has the field airln, which is the airline code and the field lnnam, which is the name of the airline. The table zflight has the field airln, the airline code and other fields which hold the details of the flights that an airline operates.
Since these 2 tables a re logically joined by the airln field, it is advisable to use the inner join.
Selectaairln alnnam bfligh bcntry into table int_airdet
From zairln as a inner join zflight as b on aairln = bairln.
In order to restrict the data as per theselection criteria, a where clause can be added to the above inner join.

13、使用sort by 代替order by

14、避免使用SELECT  DISTINCT语句
使用ABAP  SORT + DELETE ADJACENT DUPLICATES 代替.

15、内标1批量插入内标2.

INSERT LINES OF ITAB1 [FROM N1] [TO N2] INTO [TABLE] ITAB2 [INDEX IDX].

该语句要求两内表对象具有可以相互转换的行结构。如果不指定行数,则整个内表ITAB1被插入ITAB2中。不指定行数时,ITAB1可以为任意内表,ITAB2必须为索引表,但是如果需要指定起始行N1和终止行N2中的任意一个,则两个内表都必须是索引表。如果ITAB2前指定TABLE 附加项,则ITAB2可以是任意类型内表。使用该方式将一个内表插入到另个中的速度比用循环插入可以快20倍。

首先需要使用T-CODE:SE30 对程序执行时间分析,找出程序慢的部份,执行完程序可以区分三个阶段的执行时间:ABAP*、DatabaseSystem,这三个部份对效能的优先级为Database -> System -> ABAP要依序排除程序这些部份的问题。*

SE30使用方法:

点左上提示技巧,可以查看SAP所提供的一些效率比较。

输入事务代码、程序名称、功能模块名称,点Execute*,执行程序,执行完程序以后,点击左下角Analyze,可以查看程序执行效率,*

根据图形对程序的执行进行分析,点左上角 ,可以查看详细的每条语句的执行效率,

可以针对语句进行分析,如果语句执行时间过长,可以对语句或方法进行优化。

SE30分析程序可以看出哪些Table花费的成本最高,针对程序中这些SQL语法进行检查:首先要检查索引,程序中的Where子句是否用到表索引,如果没有Index*,就要评估该报表执行频率,如果很高就要建一个索引给它用。*

这里对效率比较低的语句,在网上找了一些资料,针对自己做的优化,以及SAP内自带的一些优化方法,总结了一下。

1*.最主要的是尽量减少I/O操作,然后是内存占用,再就是CPU**的负载。**CPU的负载可以通过优化程序来改善,在程序中尽量使用诸如*SUM(SQL*语句***)或者*COLLECT(ABAP语句*)***。*

2*.尽可能多地使用表的索引作为Where分句的条件选项,尽可能让程序只读取一定范围内的记录(比如说,你只准备操作一个月之内的业务数据,那么对于这一个月的业务就应该有一定的范围取值,如10002000)。*

3*.尽量使用Select A B C INTO TABLE ITAB这样的语句。这个操作会将所有符合条件的数据一次性地读进内表,这比在Select A B C INTO ITAB... ENDSELECT的循环中添加数据到内表要快。*

4*.尽可能使用Select SINGLE语句。*

5*.使用ABAP排序而不使用*order by 

6*.可以使用视图来代表基本表的查询。

7*.可以使用一些聚合函数、*GROUP BY …HAVING,来进行计算和分组统计,也可以来改善查询的效率。

例如:

不推荐

Maxnu = 0.

Select * from zflight where airln = ‘LF’ and cntry = ‘IN’.

Check zflight-fligh > maxnu.

Maxnu = zflight-fligh.

Endselect.

推荐

Select max( fligh ) from zflight into maxnu where airln = ‘LF’ and cntry = ‘IN’.

10***.使用二分法查询,提高查询内表数据速度***

不推荐

Read table int_fligh with key airln = ‘LF’.

推荐

Read table int_fligh with key airln = ‘LF’ binary search.

11*.两个内表添加使用批量增加代替逐行*

不推荐

Loop at int_fligh1.

Append int_fligh1 to int_fligh2.

Endloop.

推荐

Append lines of int_fligh1 to int_fligh2.

12*.使用*FOR ALL Entries

不推荐

Loop at int_cntry.

Select single * from zfligh into int_fligh where cntry = int_cntry-cntry.

Append int_fligh.

Endloop.

推荐

Select * from zfligh appending table int_fligh

For all entries in int_cntry

Where cntry = int_cntry-cntry.

数据——>工作区,工作区——>内表,

数据——>内表

很明显少了一个过程 效率自然高了 如果数据量越大,效果是可想而知的

13***.避免使用SELECT DISTINCT语句***

使用的** ABAP SORT + DELETE ADJACENT DUPLICATES 代替.**

14. 更多地使用动态数据对象来访问内表。

例:

不推荐:

LOOP AT itab.

**      READ TABLE itab_jest**

**          WITH KEY objnr = itab-objnr.**

**    ENDLOOP.**

***    推荐:***

**    FIELD-SYMBOLS: <ls_itab> TYPE typ_jhgb.**

** LOOP AT itab ASSIGNING <ls-itab>.**

**      READ TABLE itab_jest**

**             WITH KEY objnr = <ls-itab>-objnr.**

**    ENDLOOP.**

ABAP编程需要注意的小问题

一、Select语句中使用FOR ALL ENTRIES IN需要注意的问题

在ABAP编程中,使用FOR ALL ENTRIES IN是必不可少的语句,使用这个语句是先取出一些数据放到一个内表中,然后参考这张内表的数据取出其它的数据,这在ABAP开发中非常常用,例如:

DATA: BEGIN OF ig_bseg OCCURS 0,
werks LIKE bseg-werks,
belnr LIKE bseg-belnr,
gjahr LIKE bseg-gjahr,
dmbtr LIKE bseg-dmbtr,
END OF ig_mseg.
IF NOT ig_bkpf[] IS INITIAL.
  SELECT werks belnr gjahr dmbtr

  INTO CORRESPONDING FIELDS OF TABLE ig_bseg
  FROM bseg

  FOR ALL ENTRIES IN ig_bkpf
  WHERE werks = ig_bkpf-werks

  AND belnr = ig_bkpf-belnr

  AND gjahr = ig_bkpf-gjahr.
ENDIF.

需要注意以下问题

1、首先,必须要判断For all entries in后面的内表是否为空,如果它为空的话,那么在where条件中的与内表中字段进行比较的结果全部为真,也就是全部满足条件,这会导致取出非常多的数据,极大地影响系统的性能。

2、对于上例,按照逻辑分析可以取出某个凭证的所有行项目,但是实际情况会与你预期的不一致,如果某个凭证的多个行项目的dmbtr值是完全一样的,那么在内表ig_bseg中你只会得到一行记录,而不是多行,它自动使用了distinct,或者说删除了重复的行,这是个非常致命的问题,会导致你的程序逻辑错误,而且很难以查找,解决的办法就是要保证内表ig_bseg中取出的数据必须要有主键字段,在本例中,需要再添加buzei字段。

二、自建表和从系统外导入数据需要注意的问题
1、自建表中建立文本类型的字段(自己创建domain)时,需要注意是否允许字段可以保留文本的大小写状态,否则默认会全部转换为大写字母。

2、在某个创建的自建表中,某个字段的数据元素为MATNR(物料编号),类型为CHAR(18),但是导入的数据的长度只有CHAR(12),比数据库字段要短,如果直接导入,不进行任何处理。

**    2.1 首先导入数据肯定不会出现错误,但是在使用中会出现问题,例如我通过导入进去的物料编号找物料描述,是找不到数据的,因为我们导入物料编号的长度只有12位,而系统标准表中的物料编号前面都是用0补齐18位的,所以我们在导入数据时,有些字段不能直接写入数据库,需要进行一定的处理。**

**    2.2 我们可以调用系统的函数来实现补0这个功能。**

CONVERSION_EXIT_ALPHA_INPUT 对话退出ALPHA,外部->内部 这个是补0函数

CONVERSION_EXIT_ALPHA_OUTPUT 对话退出ALPHA,内部->外部 这个是除0函数

函数调用非常简单,如下

CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
EXPORTING
input = IG_UPLOAD-HKONT_NIS
IMPORTING
OUTPUT = WA_ZDFLNIS-HKONT_NIS\

三、提高程序运行效率的一些小方法

1、我们在ABAP程序中定义的内表,在我们从数据库中查询数据的时候,查找数据的顺序最好可以和内表定义的顺序一样,这样可以提高效率的允许效率。

2、 我们在查找簇表数据的时候,例如BSEG表,对于一个簇表来说,除了主键项目外,其他项目都被编辑到一个长文本项目中,一起存储在表簇RFBLG中的vardata项目中,这就决定了作为簇表的BSEG无法再建立键值以外的索引(INDEX),而这张表对我们来说,很多报表程序要用到这张表,而如果直接用其他项目的字段查找BSEG表的数据,效率非常的底下,所以我们需要在另外的表中找到BSEG的主键项目,来加快查询BSEG表的速度。

3、 SAP R/3系统中也有几个专门用来读取BSEG表信息的函数,可适当参考使用,它们是: READ_BSEG GET_ALL_BSEG

另外最有效率的方法是改善应用和需求,要使需求合理规范,这才能使效率达到最高化。