下一代核心

本文档简要概述了Coala的NextGen-Core。Coala的NextGen-Core承诺解除旧内核的许多限制,提高效率和性能。

有什么新鲜事吗?

作为下一代核心的一部分,添加了以下新功能:

  • 界面更简单

  • 官方支持虚拟文件

  • 改进的依赖系统

  • 新的基座轴承类型: DependencyBear

  • 能够在运行时修改BEAR依赖关系

  • 卓越的高速缓存

有什么变化了吗?

  • Global Bears 现在被称为 Project BearsLocal Bears 现在被称为 File Bears 。这两个 ProjectBear 以及 FileBear 类从新的基类继承。 coalib.core.Bear .

  • 不需要通过。 HiddenResults 在不同的熊之间(新的依赖关系管理系统使之成为可能)向结果回调隐藏结果。现在,只有通过传递所需的熊来明确请求的结果才会通过。依赖项熊可以传递任意的python对象,而不仅仅是 Result 物体。

  • 前者 run 由所有熊继承以运行代码分析的函数现在被替换为 analyze 功能。

界面更简单

可以通过仅访问一个功能来运行与NextGen-Core的Coala会话, core.run(bears) . 这个 run 方法接受参数 bearsresult_callbackcacheexecutor (最后两个是 None 默认情况下),并发起Coala会话。

  • 这个 bears 参数包含要为Coala会话运行的熊的列表。

  • 这个 result_callback 是一个在每个结果可用时立即调用的函数。它应该有以下签名:

    def result_callback(result):
        pass
    
  • 这个 cache 参数(如果提供)将启用缓存,并使用为存储BEAR结果而提供的缓存运行会话。此参数的默认值为 None 当提供时,它在没有缓存的情况下运行Coala。

  • 这个 executor 参数用于提供一个自定义执行器(在关闭内核之后关闭),传递的熊将在其中运行。如果未提供此参数,则 ProcessPoolExecutor 使用的进程数与系统上可用的核心数一样多。

与旧的熊相比,下一代核心中的熊的实现方式不同。在写下一代熊时,必须牢记以下几点:

  • 每只熊都有一个 analyze 函数来执行代码分析,而不是使用 run 在老熊身上有的功能。

  • 新的熊必须能够用来建造 sectionfile_dict 作为参数。默认参数是允许的,但不鼓励使用,因为当您的熊被用作依赖项时,您无法控制它们。

    class TestBear(Bear):
    
    def analyze(self, bear, section_name, file_dict):
        return "Some analysis"
    

有关更多详细信息,请访问 API Docs .

官方支持虚拟文件

像IntelliJ这样的IDE使用虚拟文件来表示文件系统(VFS)中的文件并对其执行操作。因此,NextGen-Core提供了对虚拟文件的官方支持。熊在运行时必须指向正确的文件数据对象,无论它们是真实文件还是虚拟文件。这使得Coala更容易与IDE集成。

任务对象

任务对象是熊执行的任务的表示。从结构上讲,它们是一个元组,包含位置参数的元组和关键字参数的字典 execute_task 函数,该函数本身调用 analyze 有了它们的高速缓存机制,它们的散列值与BEAR结果一起存储在高速缓存中,并在每次Coala运行期间进行查找以获取结果。

要清楚地了解熊的任务对象可能是什么样子,请看下面的示例 FileBear

from coalib.core.FileBear import FileBear


class SomeFileBear(FileBear):

    def analyze(self, file, filename, filename_prefix: str='',
                filename_suffix: str=''):
        yield 'Some analysis result'

其对应的任务对象如下所示:

[
    [(file, filename), {'filename_prefix': "", 'filename_suffix': ""}],
]

然后,这些任务对象可以由BEAR卸载,以便在Python池中由 generate_tasks 方法。

改进的依赖系统

NextGen-Core引入了比旧内核使用的更好的依赖管理系统。它有以下改进:

  • 熊在中指定其熊依赖项 BEAR_DEPS .

  • 一个班 DependencyTracker 管理依赖项管理。依赖项由该类添加和解析,并检查循环依赖项。

  • 使用有向图跟踪两个对象之间的依赖关系。当两个节点用有向边连接时,它们形成依赖关系。NextGen-Core取消了指定 LocalBear s作为的依赖项 GlobalBear S

这个 initialize_dependencies 方法在 Core 接收要运行的BEAR,并使用基于消费者的系统处理BEAR依赖项,以便每个依赖项BEAR在每个节和文件DICT中只有一个实例。它返回一组依赖熊以及那些没有任何依赖关系或其依赖关系已经解决的熊(这些是计划运行的熊)。在运行熊之前,我们在 __init__ 类的方法 Session 它负责运行Coala会话。

没有依赖关系或其依赖关系已解决的熊,将只计划执行它们的任务。在执行任何任务之前,Coala在缓存中查找它。在命中的情况下,使用调用缓存中存储的相应任务参数的现有结果 execute_task_with_cache 方法。在未命中的情况下,或者如果Coala在没有缓存的情况下运行,则执行任务。没有任何运行任务的熊通过解析其依赖项、调度依赖熊并将熊从 running_tasks 迪克

即使熊仍然要通过 Result 实例与Coala通信,现在可以传递任意Python对象。依赖关系熊从中受益,因为它们现在可以根据自己的需要传递数据,而不需要绑定到 Result 只有对象。

依赖关系结果位于 self.dependecy_results 并且可以通过这种方式访问。 但这是非常不受欢迎的,因为它绕过了缓存,并且在内核连续多次运行时可能会产生意想不到的结果。

DependencyBear

旧内核对BEAR依赖项的处理无效。旧的内核使用排队机制在BEAR运行之间进行通信。下一代内核在此基础上进行了改进。

引入了一种新的熊类型, DependencyBear 通过使用TASK对象传递依赖项结果,使BAR开发人员编写依赖项更加方便。这种处理依赖项的技术使 DependencyBear 以支持缓存。

这个BEAT充当一个基类,它并行处理每个依赖结果的任务。依赖于其他熊的熊可以在 BEAR_DEPS 。例如,有两只熊 FooBar 和熊熊 Bar 取决于 Foo 。这可以写成

class BarBear(DependencyBear):
    BEAR_DEPS = {FooBear}

这解决了 GlobalBear s打开 LocalBear 就在旧地核里。现在,新的依赖项管理已经就位 GlobalBear s不会因为LocalBear运行终止而停止。这消除了旧核心面临的所有同步问题。

多个熊可以作为一个熊的依赖项包含在 BEAR_DEPS 田野。依赖关系熊的结果保存在名为 _dependency_results 它在 __init()__ 类的方法 Bear 并且可以使用该方法访问 dependency_results() 也属于同一个班级。

编写依赖熊(DependencyBear)

让我们考虑一只熊依赖于一个项目熊 Fizz 和一个档案袋 Buzz 然后将对应的DependencyBear称为 FizzBuzz 将如下所示:

class FizzBear(ProjectBear):

    def analyze(self, file, filename):
        yield 'Fizz analysis'
class BuzzyBear(FileBear):

    def analyze(self, file, filename):
        yield 'Buzz analysis'
class FizzBuzzBear(DependencyBear):
    BEAR_DEPS = {FizzBear, BuzzBear}

    def analyze(self, dependency_bear, dependency_result, a_number=100):
        yield '{} ({}) - {}'.format(
            dependency_bear.name, a_number, dependency_result)

能够在运行时修改Bear依赖项

一只熊可能要依靠多只熊才能开始行刑。 Bear.BEAR_DEPS 只是一组在熊可以运行之前需要执行的熊类。一旦运行了所有这些依赖项,它们的结果就会追加到 self.dependency_results 。结果以字典的形式出现,熊的类型和它们的相应结果(以列表的形式)如下 key-value 成对的。在上一个示例中,如果我们尝试访问 BarBear 我们会得到结果的 {{<class 'coalib.core.Bear.FooBear'>}} .

__init__() 类的方法 Bear 中指定的依赖项 BEAR_DEPS 复制到Bear run的每个实例,这使得运行时修改成为可能。

凌驾于熊之上

下一代BEAR必须具有以下功能才能执行分析:

  • analyze :此方法包含执行该轴承所用于的实际代码分析例程的代码。

  • generate_tasks :此方法是父级的一部分 Bear 类,并返回元组形式的位置参数和字典形式的关键字参数的元组。这些实际上是由内核调度和执行的任务对象。如果没有这种方法,就会引发 NotImplementedError (需要记住的一件事是,您需要实现 generate_task 只有在其他BEAR基类没有提供正确的并行化级别的情况下。)

从这个阶级继承下来的一只熊 FileBear 可以并行处理每个给定文件的任务。从这个阶级继承下来的一只熊 DependencyBear 可以针对每个依赖项结果并行化任务。从这个阶级继承下来的一只熊 ProjectBear 不并行每个文件的任务,因为它在给定的整个代码库上运行。

让我们用习惯来写我们自己的熊吧 generate_tasks 方法。我们会给这头熊取名为 PairWiseDependencyBear 它将比较由它的两个依赖熊生成的结果。(这种承载在代码克隆检测的情况下可能很有用)。

# This bear provides some code analysis
class SomeDependencyBear(Bear):

    def analyze(self, bear, section_name, file_dict):
        yield 'Some analysis result'
# This bear provides some code analysis
class SomeOtherDependencyBear(Bear):

    def analyze(self, bear, section_name, file_dict):
        yield 'Some more analysis result'
# This bear depends on the above bear and performs some
# more analysis after receving its results
class PairWiseDependencyBear(Bear):
    BEAR_DEPS = {SomeDependencyBear, SomeOtherDependencyBear}

    def analyze(self, file, filename):
        return 'More analysis'

    def generate_tasks(self):
        similar_results = []
        results = [r['SomeDependencyBear'] for r in self.dependecy_results]
        other_results = [r['SomeOtherDependencyBear']
                   for r in self.dependecy_results]

        for a, b in zip(results, other_results):
            if a == b:
                similar_results = a

        # returns some kind of task object containing
        # the results common to both dependency bears
        # and their corresponding lengths
        return (((i, len(i)), {}) for i in similar_results)

卓越的高速缓存

NextGen-Core的缓存可以分为两个部分:

  • 缓存 File 物体

  • 任务对象的缓存

由于NextGen-Core超过了熊市 File 对象与文件交互, File 使用自己的缓存机制确保高性能I/O操作。每当访问属性时,都会返回缓存结果,而不是再次加载文件。有关更多详细信息,请查看 IO docs .

NextGen-Core的主要缓存机制是基于任务对象的。熊可以通过以下方式卸载任务 generate_tasks() 它们由Python池执行。就结构而言,缓存是一个类似字典的对象,将Bear类型和缓存表作为键值对。缓存表本身是类似字典的对象,它们映射任务对象的散列值(由生成 PersistentHash.persistent_hash )向熊市的结果致敬。

在调度熊时,内核执行缓存查找。如果将参数设置为 execute_tasks() 与前一次运行的结果相同(换句话说,它在缓存中查找相同的任务对象,如果找到,则获取其相应的结果),那么我们将获得该BEAR的缓存结果,而不是再次执行该BEAR。

下一代核心预计 analyze 每个轴承的功能提供只依赖于输入参数的结果。换句话说,就是 analyze 应将其参数映射到结果。禁止在不将时间相关数据放入任务对象的情况下使用易变的值,因为这可能会导致Coala中的未知行为。