微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

优化用 TypeScript 编写的文件内容解析器类 第一:Typescript 真的是 Javascript第二:仅当任务受 CPU 限制时才使用工作线程

如何解决优化用 TypeScript 编写的文件内容解析器类 第一:Typescript 真的是 Javascript第二:仅当任务受 CPU 限制时才使用工作线程

我有一个 typescript 模块(由 VSCode 扩展使用),它接受一个目录并解析文件中包含的内容。对于包含大量文件的目录,此解析需要一些时间,因此需要一些有关如何优化它的建议。

我不想复制/粘贴整个类文件,因此将使用包含我认为相关部分的模拟伪代码

class Parser {
    constructor(_dir: string) {
        this.dir = _dir;
    }

    parse() {
        let tree: any = getfiletree(this.dir);

        try {
            let parsedobjects: MyDTO[] = await this.iterate(tree.children);
        } catch (err) {
            console.error(err);
        }
    }

    async iterate(children: any[]): Promise<MyDTO[]> {
        let objs: MyDTO[] = [];

        for (let i = 0; i < children.length; i++) {
            let child: any = children[i];

            if (child.type === Constants.FILE) {
                let dto: FileDTO = await this.heavyFileProcessingMethod(file); // this takes time
                objs.push(dto);
            } else {
                // child is a folder
                let dtos: MyDTO[] = await this.iterateChildItems(child.children);
                let dto: FolderDTO = new FolderDTO();
                dto.files = dtos.filter(item => item instanceof FileDTO);
                dto.folders = dtos.filter(item => item instanceof FolderDTO);
                objs.push(FolderDTO);
            }
        }

        return objs;
    }

    async heavyFileProcessingMethod(file: string): Promise<FileDTO> {
        let content: string = readFile(file); // util method to synchronously read file content using fs

        return new FileDTO(await this.parseFileContent(content));
    }

    async parseFileContent(content): Promise<any[]> {
        // parsing happens here and the file content is parsed into separate blocks
        let ast: any = await convertToAST(content); // uses an asynchronous method of an external dependency to convert content to AST
        let blocks = parsetoBlocks(ast); // synchronous method called to convert AST to blocks

        return await this.processBlocks(blocks);
    }

    async processBlocks(blocks: any[]): Promise<any[]> {
        for (let i = 0; i < blocks.length; i++) {
            let block: Block = blocks[i];
            if (block.condition === true) {
                // this can take some time because if this condition is true,some external assets will be downloaded (via internet) 
                // on to the caller's machine + some additional processing takes place
                await processBlock(block);
            }
        }
        return blocks;
    }
}

仍然是 TypeScript/NodeJS 的初学者。如果可能的话,我正在这里寻找多线程/Java 风格的解决方案。在 Java 的上下文中,this.heavyFileProcessingMethod 将是 Callable 对象的实例,该对象将被推入 List<Callable>,然后由 ExecutorService 并行执行,返回 { {1}}。

基本上我希望所有文件都被并行处理,但函数必须等待所有文件在从方法返回之前被处理(所以整个 List<Future<Object>> 方法只需要解析所花费的时间最大的文件)。

一直在阅读 running tasks in worker threads in NodeJS,这样的东西也可以在 TypeScript 中使用吗?如果是这样,它可以在这种情况下使用吗?如果我的 iterate 类需要重构以适应此更改(或任何其他建议的更改),这没有问题。

编辑:使用 Parser

Promise.all

解决方法

第一:Typescript 真的是 Javascript

Typescript 只是带有静态类型检查的 Javascript,当 TS 转换为 JS 时,这些静态类型会被删除。由于您的问题是关于算法和运行时语言功能的,因此 Typescript 没有任何意义;你的问题是一个 Javascript 问题。所以马上告诉我们答案

一直在阅读有关在 NodeJS 工作线程中运行任务的文章,这样的东西也可以在 TypeScript 中使用吗?

是。

关于你问题的第二部分,

在这种情况下可以使用吗?

答案是是的,但是...

第二:仅当任务受 CPU 限制时才使用工作线程。

可以并不一定意味着你应该。这取决于您的进程是IO 绑定 还是CPU 绑定。如果它们是 IO 绑定的,那么依赖 Javascript 长期存在的异步编程模型(回调、承诺)很可能要好得多。但是如果它们受 CPU 限制,那么使用 Node 对基于线程的并行性的相对较新的支持更有可能导致吞吐量增加。参见Node.js Multithreading!,不过我认为这个更好:Understanding Worker Threads in Node.js

虽然工作线程的重量比以前用于并行性(产生子进程)的 Node 选项轻,但与 Java 中的线程相比,它的重量仍然相对较重。每个工作程序都在自己的节点 VM 中运行,不共享常规变量(您必须使用特殊数据类型和/或消息传递来共享数据)。必须以这种方式完成,因为 Javascript 是围绕单线程编程模型设计的。它在该模型中非常高效,但这种设计使得对多线程的支持更加困难。这是一个很好的 SO 答案,其中包含对您有用的信息:https://stackoverflow.com/a/63225073/8910547

我的猜测是您的解析更受 IO 限制,并且产生工作线程的开销将超过任何收益。但是试一试,这将是一次学习经历。 :)

,

看起来您最大的问题是导航嵌套的目录结构并保持对每个文件和每个目录的承诺有条理。我的建议是以更简单的方式做到这一点。

有一个函数,它接受一个目录路径,并以类似于 find 程序的方式返回它可以找到的所有文件的平面列表,无论多深。这个函数可以是这样的:

import * as fs from 'fs/promises'
import * as path from 'path'
    
async function fileList(dir: string): Promise<string[]> {
    let entries = await fs.readdir(dir,{withFileTypes: true})

    let files = entries
        .filter(e => e.isFile())
        .map(e => path.join(dir,e.name))
    
    let dirs = entries
        .filter(e => e.isDirectory())
        .map(e => path.join(dir,e.name))

    let subLists = await Promise.all(dirs.map(d => fileList(d)))

    return files.concat(subLists.flat())
}

基本上,获取目录条目,查找(子)目录并并行递归迭代它们。迭代完成后,展平、合并并返回列表。

使用此功能,您只需使用 map + Promise.all 即可一次性将繁重的任务应用到所有文件:

 let allFiles = await fileList(dir)

 let results = await Promise.all(allFiles.map(f => heavyTask(f)))

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。