An extensive study on pre-trained models for program understanding and generation
Zhengran Zeng1, Hanzhuo Tan2, Haotian Zhang3, Jing Li4,Yuqun Zhang1,Lingming Zhang5
1Southern University of Science and Technology, China
2Southern University of Science and Technology and The Hong Kong Polytechnic University
3Kwai Inc. China
4The Hong Kong Polytechnic University, China
5University of Illinois Urbana-Champaign, United States
引用
Zeng Z, Tan H, Zhang H, et al. An extensive study on pre-trained models for program understanding and generation[C]//Proceedings of the 31st ACM SIGSOFT international symposium on software testing and analysis. 2022: 39-51.
论文:https://doi.org/10.1145/3533767.3534390
仓库:https://github.com/ZZR0/ISSTA22-CodeStudy
摘要
自动程序理解和生成技术可以显着提高程序员的生产力,并已被学术界和工业界广泛研究。最近,预训练范式的出现启发研究人员开发通用的预训练模型,该模型可应用于广泛的程序理解和生成任务。这种预先训练的模型是通过大型未标记语料库的自监督目标得出的,可以在下游任务(例如代码搜索和代码生成)中以最小的适应进行微调。尽管这些预训练模型声称优于现有技术,但它们很少遵循等效的评估协议,例如,它们很难在相同的基准、任务或设置上进行评估。因此,迫切需要对预训练模型的有效性、通用性以及局限性进行全面研究,以便为该领域的未来发展提供启示和指导。为此,我们首先在七个代表性代码任务的大型基准上对八个开放访问预训练模型进行了广泛的研究,以评估其可重复性。我们进一步比较预训练的模型和特定领域的最先进技术,以验证预训练的有效性。最后,我们通过检查预训练模型在对抗性攻击下的性能变化来研究预训练模型的鲁棒性。通过研究,我们发现,虽然我们通常可以复制预训练模型在其评估任务和采用的基准上的原始性能,但细微的性能波动可以反驳其原始论文中的发现。此外,现有的预训练模型都无法凌驾于所有其他模型之上。我们还发现,在程序理解任务中,预训练的模型可以显著优于非预训练的最先进技术。此外,我们通过对抗性攻击对自然语言编程语言预训练模型的鲁棒性进行了首次研究,发现简单的随机攻击方法可以轻松欺骗最先进的预训练模型,从而引发安全问题。最后,我们还提供了多种实用指南,以推进未来对用于程序理解和生成的预训练模型的研究。
1 引言
几十年来,程序理解和生成的研究任务,例如代码摘要、代码生成、代码搜索和缺陷预测,得到了越来越多的研究。此外,深度学习和机器学习技术的出现有力地推动了这些研究领域的进步。最近,一组研究人员提出了自然语言(NL)-编程语言(PL)预训练模型,以提供自然语言和程序语言之间语义连接的通用表示,支持各种程序理解和生成任务。与需要特定任务特征提取或模型选择的传统深度学习技术相比,此类 NL-PL 预训练模型通常源自大型未标记程序语言语料库上的自监督语言建模任务,只需简单的修改和低成本微调。具体来说,他们倾向于采用流行的 Transformer 架构,例如 BERT 和 GPT 进行预训练,以促进程序理解和生成任务的性能。例如,CodeBERT 通过两个自监督任务对数百万个程序函数和自然语言注释进行了预训练 - 掩码语言建模(即随机掩码输入标记)和替换标记检测(即,随机替换输入标记)来预测输入标记的屏蔽/替换标记。预训练后,CodeBERT 模型针对不同任务进行微调。此外,最近提出的CodeX是一种基于GPT的模型,在Github上的大量代码样本上进行了预训练,在自动编程任务上自动修复了HumanEval数据集中28.8%的编程问题。
尽管原始论文中展示了 NL-PL 预训练模型的有效性和多功能性,但它们的性能评估仍然容易受到偏差的影响。具体来说,虽然 NL-PL 预训练模型可以应用于许多潜在的任务,但它们几乎不能应用于完全相同的任务以进行全面的性能比较和分析。此外,即使对于某些相同的任务进行评估,它们有时也会采用不同的基准,因此它们的性能也很难精确比较。此外,许多研究表明,自然语言处理的通用语言模型本质上是有缺陷的。例如,它们的功效可能很难重现。此外,在一组行为测试中,它们对噪音和攻击非常敏感。这些事实表明,迫切需要对现有的 NL-PL 预训练模型进行广泛的研究。在本文中,据我们所知,我们对现有的 NL-PL 预训练模型进行了首次全面的研究,以增强对其优点和局限性的理解。具体来说,我们的研究目标包括 (1) 验证不同 NL-PL 预训练模型的性能,(2) 将此类模型与之前的特定领域最先进 (SOTA) 模型进行比较,以及 ( 3)研究预训练模型的稳健性。请注意,尽管现有的工作(例如 CodeXGLUE)也提出了对 NL-PL 预训练模型的实验研究,但它们几乎无法在通用基准上同时评估所有研究的模型,并且无法对它们提供有洞察力的分析。
为此,我们首先决定广泛研究预训练模型的有效性,这些模型具有扩展的任务和数据集,这些任务和数据集尚未被任何先前的工作探索过。特别是,我们采用了八种主流的预训练模型,即CodeBERT、CodeGPT、CodeT5、CodeTrans、ContraCode、CoTexT、GraphCodeBERT、和 PLBART作为我们的研究对象,因为它们是公开可用的 SOTA 模型,专为广泛的程序理解和生成任务而设计。然后,我们在基准 CodeXGLUE 的基础上评估所有研究的预训练模型,因为它的包容性数据集已被许多研究模型广泛采用,并且它还提供了确定性的训练/测试数据分割,以加强其公平性。性能比较。与此同时,我们在扩展基准的基础上,针对单个程序理解和生成任务的非预训练 SOTA 技术,评估了所研究模型的有效性。接下来,我们通过使用多种现有方法和我们论文中提出的简单随机攻击方法进行对抗性攻击,进一步检查所研究模型的鲁棒性。
我们的研究揭示了设计和开发未来 NL-PL 预训练模型的多个重要且有趣的见解。具体来说,我们发现没有任何预训练模型可以在所有研究的下游任务上支配其他模型,并且它们的有效性可能会受到损害。更具体地说,虽然我们研究的预训练模型的性能通常可以在其最初采用的基准上复制,但细微的性能波动可能会反驳原始论文中所研究模型的性能比较结果。例如,尽管 PLBART 报告其性能优于 CodeBERT,但我们的研究表明它在缺陷检测和代码搜索方面无法超越 CodeBERT。我们还发现,几乎不存在占主导地位的预训练模型,并且具有多个目标的预训练(例如,基于编码器-解码器的模型)可能会损害各个目标的预训练能力。此外,通过与特定领域的 SOTA 技术进行比较,我们发现预训练的模型可以在多个研究任务上实现出色的性能,例如,CodeGPT 在克隆检测和代码搜索方面比所采用的数据集提高了 10.18% 和 63.94%在相应的SOTA技术的原始论文中。最后,我们提出了一种新的对抗性攻击框架来评估预训练模型的鲁棒性,并首次证明,尽管所研究的模型具有出色的性能,但可以通过简单的随机攻击方法轻松攻击。有趣的是,尽管某些模型(例如 GraphCodeBERT)通过注入辅助信息(即 DFG)进行建模,比现有模型(例如 CodeBERT)提高了性能,但它更容易受到对抗性攻击。因此,我们的研究还揭示了在不久的将来推进 NL-PL 预训练模型的各种实用指南。
总而言之,我们的论文做出了以下贡献。
数据集:对于常见的研究任务,我们收集了广泛的数据集,包括广泛使用的数据集 CodeXGLUE 和来自特定领域技术的数据集,以便为每个任务分配通用且足够的数据集来评估多个 NL-PL 预训练模型。研究:对所提出的数据集使用通用 NL-PL 预训练模型进行了广泛的研究,这有助于 (1) 验证和校准原始论文的性能,(2) 与原始论文相比的全面和标准化的实验研究,(3)首次对 NL-PL 预训练模型采用对抗性攻击来研究其鲁棒性。影响:这项工作揭示了多种含义,包括(1)在发布预训练模型之前,可靠且可复制的实验至关重要,因为多次运行中细微的性能波动很容易覆盖其性能比较; (2) 提出主导的通用模型可能具有挑战性——令人惊讶的是,最先进的基于编码器-解码器的模型可能会损害其编码器组件,并且在多程序理解和理解方面,其性能甚至比纯基于编码器的模型更差。生成任务; (3) 应更多地探索预训练模型,因为它们在多个下游任务上往往优于传统的 SOTA 技术;(4)作为第一个通过对抗性攻击来研究 NL-PL 预训练模型鲁棒性的研究,我们发现现有的预训练模型(即使是那些具有提高模型鲁棒性的特殊策略的模型)相当脆弱。2 研究对象和数据集
2.1 研究对象
自EMNLP 20 提出 CodeBERT 后,NL-PL 预训练模型(后面简称“预训练模型”或简称“模型”)的研究成为热点,模型不断涌现。虽然可能有许多潜在的最先进的预训练模型需要研究,但我们选择过滤它们来评估具有代表性、公开可用且专为广泛的程序理解而设计的模型。和生成任务进行广泛的研究。例如,CuBERT 与 CodeBERT 的唯一不同之处在于 CodeBERT 额外采用了 RTD(替换令牌检测)预训练目标。此外,TransCoder 仅针对代码翻译任务而设计。根据之前的工作,我们将它们排除在我们的研究之外。因此,我们选择了八个最近提出的预训练模型作为我们的研究对象,即 CodeBERT、GraphCodeBERT、ContraCode(全部作为基于编码器的模型)、CodeGPT(基于解码器的模型)、CodeT5、CodeTrans、CoTexT 和PLBART(全部作为基于编码器-解码器的模型)。
CodeBERT。遵循与 BERT 相同的架构,即基于 Transformer 的编码器,CodeBERT 采用 Masked Language Modeling和替换令牌检测(即随机替换令牌来训练检测标记是否被替换的模型)作为预训练任务。GraphCodeBERT。 ICLR 21 中提出的 GraphCodeBERT 采用基于 Transformer 的编码器架构,并在边缘预测(即屏蔽数据流边缘中的变量)和节点对齐(即预测跨源代码和数据流的变量对齐)作为预训练任务。ContraCode。受对比学习的启发,ContraCode 于 2021 年 4 月提出,通过执行语义保留转换来增强训练数据。除了掩码语言建模目标之外,它还采用 InfoNCE损失来进行对比训练样本对进行预训练。 CodeGPT。 NeurIPS 21 中提出的 CodeGPT 在编程语言上预训练基于 Transformer 的解码器语言模型 GPT。其预训练过程遵循与图 1b 所示相同的方案。 PLBART。 NACCL’21 中提出的 PLBART 是一种受 BART 启发的基于变压器的编码器-解码器。它遵循与 BART 类似的预训练任务,包括令牌屏蔽、令牌删除和令牌填充。为搜索提供被测模块的预期用途示例可以让 SBST 避免其覆盖范围停滞。CodeTrans。CodeTrans于2020年9月提出,是遵循T5的模型设置的基于Transformer的编码器-解码器。它采用跨度掩码(即随机掩码整个标记的范围,而不是掩码语言建模中的单个元素)和语言建模(即自回归语言生成)作为训练模型的目标。CoTexT 。 NLP4Prog 21 中提出的 CoTexT 也构建在与 T5 相同的架构上。它在原始 T5 检查点之上使用 NL-PL 数据预训练模型。CodeT5。 EMNLP 21 中提出的 CodeT5 遵循与 T5 相同的编码器-解码器架构。 CodeT5 提出了一种新颖的标识符感知预训练任务来利用特定于代码的结构信息以及双模态双生成预训练任务来增强 NL-PL 对齐。表1 预训练模型总结
表 1 总结了我们研究对象的特征。虽然包含六种程序语言和 2.3M PL-NL 函数对的 CodeSearchNet 被广泛用作预训练语料库,但不同的模型实际上采用了相当不同的训练数据子集。例如,CodeBERT 仅使用 2.1M PL-NL 函数对,而 ContraCode 选择 1.8M 程序函数和仅 80K 自然语言注释。此外,我们可以清楚地观察到,在他们的原始论文中,他们没有采用通用的数据集或任务来评估其有效性,例如,CodeBERT 最初是在 CodeSearchNet 上评估的,而 ContraCode 则结合了不同的数据源来进行类型推断和代码摘要,还收集用于克隆检测的数据集。这些事实表明迫切需要统一评估协议,以便在它们之间进行公平的比较。
2.2 数据集
我们首先的目标是采用一个常用的基准,可以对所研究的预训练模型进行广泛的评估。特别是,我们采用了 CodeXGLUE 数据集,这是微软为程序理解和生成任务构建的基准,它收集了以前研究的 14 个数据集。请注意,选择 CodeXGLUE 是因为它的许多包容性数据集已被用于本文中多个研究模型的性能评估。此外,它为所有模型提供确定性的训练/测试数据分割方案,以加强其性能比较的公平性。具体来说,我们采用 BigCloneBench和 POJ-104进行克隆检测,Devign进行缺陷检测,Bugs2Fix进行代码修复,CodeTrans进行代码翻译,CodeSearchNet-AdvTest进行代码翻译。代码搜索,以及用于代码摘要的 CodeSearchNet-Java。这样的数据集被我们在本文中的研究对象及其原始论文中采用,因此可以促进大多数原始评估的复制,并进一步扩展所有预训练模型的研究。
此外,为了在预训练模型和非预训练 SOTA 模型之间进行公平比较,除了我们的 CodeXGLUE 基准之外,我们还在原始论文中包含了用于评估 SOTA 模型的 4 个数据集。因此,预训练模型和 SOTA 模型都可以在最初采用的项目和扩展项目上进行评估。特别是,我们添加了用于缺陷检测的 Reveal [10] 数据集、用于克隆检测的 POJ-15数据集、用于代码搜索的 DGMS-Java数据集以及用于代码摘要的 Rencos-Java数据集。
3 实验评估
3.1 实验设置
研究问题。我们的评估调查了以下问题。
RQ1:预训练模型如何执行程序理解和生成任务?对于此RQ,我们根据 CodeXGLUE 基准广泛研究了所有采用的预训练模型之间的性能评估。
RQ2:预训练模型与非预训练模型相比表现如何?对于此 RQ,我们评估并比较了预训练模型和特定领域的非预训练最先进 (SOTA) 模型在多个任务上的性能。
RQ3:预训练模型是否稳健?对于此RQ,我们应用保留语义的对抗性攻击技术来研究所研究模型的鲁棒性。
RQ1 预训练模型如何执行程序理解和生成任务?
虽然所研究的预训练模型研究了不同的程序理解和生成任务,但我们决定在提供的微调过程下评估由多个研究对象研究过的任务,即在之前的工作 CodeXGLUE 中广泛研究和比较了它们的性能。考虑到模型的随机性,我们按照先前工作的建议运行所有实验 5 次,并给出平均结果,而我们研究的所有预训练模型都没有在原始论文中明确表明其运行次数。
表2 代码理解任务上的评估结果
表3 代码生成任务上的评估结果
表 2 和表 3 分别展示了基于 CodeXGLUE 基准的程序理解和生成任务的平均实验结果。具体来说,我们采用数据集BigClone-Bench来评估克隆检测作为分类问题,即确定一对代码片段是否被克隆,其中�,�和�1用作评估指标。我们还采用数据集 POJ-104 将克隆检测评估为检索问题,即从整个测试集中检索给定代码片段的克隆样本,其中 � 用作评估指标。此外,对于代码修复,我们在表 3 中采用了两个不同大小的数据集(在中标记为具有 58350 个样本的小型数据集和具有 65454 个样本的中型数据集)来评估它们如何影响所研究的模型。我们还评估了双向代码翻译任务,即从 Java 到 C 以及从 C 到 Java 的转换。同时,通过将我们的评估结果与他们原始论文中提出的相应结果进行比较,我们在颜色梯度方面标记了他们的数值变化,如表2的“间隙颜色条”所示,其中颜色梯度代表了我们的研究结果减去原始结果。例如,在我们的研究中,GraphCodeBERT 在克隆检测下的 �F1分数下降了 0.72,并在表 2 中表示为浅黄色。此外,CodeTrans 在代码摘要下的�-4 分数在我们的研究中增加了 0.20,并表示为浅绿色如表3所示。请注意,其余未标记的统计数据是指我们的扩展评估结果,未包含在相应的原始论文中。
我们发现我们的大部分结果都能与原始结果保持一致。例如,在我们的研究中,�F1得分为 96.38,在 BigCloneBench 下进行克隆检测的 GraphCodeBERT 原始论文中,F1得分为 97.10。此外,在我们的研究中,代码摘要下的BLEU�-4 得分为 20.39,CodeTrans 原始论文中的得分为 20.19。同时,我们的实验结果与原始结果的平均绝对差异为1.37%,这表明我们的实验结果与原始结果没有显着差异,我们可以将研究模型的性能整体复制为原始论文。
图3 BigCloneBench 下克隆检测的最小-最大 F1 变化
然而,我们研究中的多项性能比较结果与他们的原始论文相比是可以逆转的。具体来说,图 3 显示了 BigCloneBench 下 5 次克隆检测运行的平均 �1 和最小-最大变化。我们注意到,所有执行的性能差异对不同模型之间的性能比较造成了不可忽略的影响。例如,CodeGPT 的性能差异最大,即它可以在一次运行中优于所有其他预训练模型,而在另一次运行中则表现不佳。此外,尽管 PLBART 在其原始论文中报告了比 CodeBERT 0.7 �1 的优势,但我们研究中 PLBART 的最小-最大差距(即图 3 中的 0.84)可能很容易使这样的结果站不住脚。此外,表 2 显示我们的研究中 CodeBERT 和 PLBART 的缺陷检测精度分别为 63.68 和 60.77,而 PLBART 论文中的缺陷检测精度分别为 62.08 和 63.18。尽管这种波动看起来很小/有限,但它们可以轻松扭转性能比较结果,并可能损害 PLBART(和其他一些模型)的强度。
RQ2 预训练模型与非预训练模型相比表现如何?
预训练模型预计具有突出的性能,因为与非预训练模型相比,它们可以有效地从大量预训练数据中捕获丰富的知识(例如语法和语义)(例如,预训练的训练语料库大小为数百万)表 1 中的训练模型与表 4 中的数万个非预训练模型)。这些数据通过大量参数隐式编码,最终可以促进各种下游任务。为了验证此类举措,必须比较预训练模型和特定领域的非预训练 SOTA 方法之间的性能。为此,我们搜索了顶级学术场所最近发表的论文,以确定所有提出的任务,它们可以胜过许多众所周知的先前发表的技术。因此,我们还尝试通用的 SOTA 技术,以便将性能比较限制在相同的范围内。最终,我们采用这些 SOTA 技术,包括:Reveal、FCDetector、DGMS、Rencos等。
据我们所知,此类方法是迄今为止其领域中最新且备受推崇的工作。另一方面,我们找不到合适的非预训练模型来进行代码生成、代码修复和代码翻译。特别是,SOTA代码生成方法NL2Code依赖于外部知识,例如特定程序语言的API文档来生成代码,而SOTA代码修复方法需要精确的故障定位信息,因此很难扩展到CodeXGLUE数据集。虽然我们可以追溯到早期的特定领域技术,即 CONCODE、CODETRANS,它们采用基于 RNN 的 seq2seq 模型,但之前的研究表明预训练模型使用 Transformer 架构在程序生成任务中可以大大优于它们。对于代码翻译,最新的研究工作还表明,预训练的模型可以优于非预训练的特定领域技术。
表4 在扩展数据集上评估预训练和非预训练的 SOTA 模型
表4展示了预训练和非预训练SOTA模型的性能比较结果。请注意,我们采用 POJ-15 来评估克隆检测作为对应于表 2 中 BigCloneBench 的分类问题,而 POJ-104 用于基于检索的克隆检测。通过比较BigCloneBench下的克隆检测结果,我们观察到CodeBERT和GraphCodeBERT的性能不能推广到扩展的POJ-15数据集。特别是,GraphCodeBERT 在数据集 POJ-104 下实现了 89.97 的最佳�,在克隆检测数据集 BigCloneBench 下实现了 96.38 。然而,其 �F1 分数在 POJ-15 数据集下下降至 66.80。 CodeBERT(从 96.22 到 55.46)、ContraCode(从 95.96 到 50.47)和 CodeTrans(从 92.93 到 66.73)也出现了这种急剧的性能下降。上述结果可能是由于直接从原始论文中采用的超参数对于我们的数据集并不都是最佳的,例如,它更适合 BigCloneBench 下的上述模型而不是 POJ-15。因此,我们推断,即使对于各种基准通用的预训练模型也可能会导致严重的性能下降,即预训练模型是数据依赖的。
我们可以观察到,对于大多数任务,预训练模型的性能可以显着优于特定领域的 SOTA 模型。特别是,对于缺陷检测,CodeBERT 和 CoTexT 在 CodeXGLUE 和 Reveal 下分别实现了 63.68 和 90.72 的最佳�。这样的性能可以分别优于SOTA技术Reveal 11.78%和4.00%。对于 POJ-104 下的克隆检测,GraphCodeBERT 的 �MAP为 89.97,大大超过了非预训练的 SOTA 模型 FCDetector,后者获得了 27.49。同时,对于 POJ-15 下与 FCDetector 论文设置相同的克隆检测,CodeGPT 和 PLBART 在�F1、精度和召回率方面均达到 100,远远优于 FCDetector。请注意,与 BigCloneBench 相比,POJ-15(在 FCDetector 中引入)是一个更小、更简单的数据集,即 1510 个代码片段与 9126 个代码片段。此外,POJ-15存在数据泄漏问题,即在训练过程中可以看到部分测试样本,而BigCloneBench则不会出现这样的问题。因此,CodeGPT 和 PLBART 可以在 POJ-15 上实现出色的性能,而其他代码则由于数据依赖性问题而无法实现。
注意到对于代码摘要,SOTA Rencos 在其原始论文采用的数据集下比 GraphCodeBERT 表现好 35.64%,在 CodeXGLUE 数据集中比 GraphCodeBERT 低 19.79%。我们假设这种性能可能是由于 Rencos 非常依赖数据造成的。具体来说,在总结目标程序时,Rencos首先从训练数据集中检索相似的代码片段,并将它们作为其采用的神经网络进行代码总结的输入的一部分。因此,它可以在训练和测试数据集中包含大量重复样本的数据集中实现卓越的性能。
总而言之,我们推断预训练模型在程序理解任务中通常优于非预训练 SOTA 模型。由于它们能够实现强大的性能增益,因此通过逐步升级现有技术来建立特定领域的 SOTA 技术可能需要很长的路要走。因此,我们建议研究人员在预训练模型的研究上付出更多的努力。
RQ3 预训练模型是否稳健?
预训练模型在不同数据集下的性能可能非常不一致,这意味着潜在的模型鲁棒性问题。这些问题可能会严重阻碍预训练模型的实际使用,特别是当它们很容易被操纵/欺骗时。以缺陷检测为例,精明的开发人员可能会通过保留语义的编辑(如表5所示)来欺骗反缺陷系统,以避免额外的代码审查过程。此类技巧可能会增加程序崩溃或死锁的威胁。作弊过程可以被视为对预训练模型的对抗性攻击。因此,我们决定应用保留语义的对抗样本来攻击预先训练的模型以检查其鲁棒性。
表5 评估缺陷检测和代码摘要下的鲁棒性
然而,据我们所知,在本研究之前,还没有专门为 NL-PL 预训练模型设计的对抗性攻击方法。特别是,在编程语言领域,虽然之前针对编程语言的攻击方法采用了one-hot embedding ,但所有现有的NL-PL预训练模型都采用了连续向量嵌入,这使得单热嵌入攻击不适用。对于自然语言中的对抗性攻击,模型可以修改句子中的每个单词。在 NL-PL 预训练模型中简单地遵循这种无约束的操作可能会编辑程序的关键字,例如“for”和“while”,从而可能破坏程序语义并使程序执行失败。为了安全地进行对抗性攻击,保留预训练模型的程序语义和语法,我们提出了一个基于 AdvCG 的新框架(最初是为非预训练模型设计的,用于搜索和查找可以安全使用的站点)程序中修改)和来自 NLP 的攻击方法(修改具有最佳扰动的站点的对抗性攻击方法)。AdvCG 广泛用于编程语言的攻击方法。
根据构造的方法,我们有了如下发现:1. 首次证明 NL-PL 预训练模型并不稳健。它们非常容易受到保留语义的对抗性样本的攻击。2. 当前提高预训练模型鲁棒性的策略,例如考虑高级学习策略或附加代码语义,效果有限。我们呼吁未来研究更强大的预训练模型。3. 简单的随机攻击方法对于攻击 NL-PL 预训练模型来说已经相当强大了。4. 对于 NL-PL 预训练模型,转换方法往往比搜索方法对攻击性能的影响更大。
转述:尚也