7

我有一个打字稿模块(由 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将是Callableobject 的实例,并且该对象将被推入 a List<Callable>,然后由ExecutorService返回的List<Future<Object>>.

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

一直在阅读NodeJS 中工作线程中的运行任务,这样的东西也可以在 TypeScript 中使用吗?如果可以,可以在这种情况下使用吗?如果我的Parser课程需要重构以适应此更改(或任何其他建议的更改),那没问题。

编辑:使用Promise.all

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

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

        if (child.type === Constants.FILE) {
            let promise: Promise<FileDTO> = this.heavyFileProcessingMethod(file); // this takes time
            promises.push(promise);
        } else {
            // child is a folder
            let dtos: Promise<MyDTO>[] = this.iterateChildItems(child.children);
            let promise: Promise<FolderDTO> = this.getFolderPromise(dtos);
            promises.push(promise);
        }
    }

    return promises;
}

async getFolderPromise(promises: Promise<MyDTO>[]): Promise<FolderDTO> {
    return Promise.all(promises).then(dtos => {
        let dto: FolderDTO = new FolderDTO();
        dto.files = dtos.filter(item => item instanceof FileDTO);
        dto.folders = dtos.filter(item => item instanceof FolderDTO);
        return dto;
    })
}
4

2 回答 2

4

第一:Typescript 真的是 Javascript

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

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

是的

至于你问题的第二部分,

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

答案是肯定的,但是...

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

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

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

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

于 2021-05-24T12:00:38.287 回答
4

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

有一个函数,它接受一个目录路径并以类似于程序的方式返回它可以找到的所有文件的平面列表,无论多深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)))
于 2021-05-27T02:07:05.160 回答