简体   繁体   English

C:每个子进程读取备用行

[英]C: Each child process reads alternate lines

I'm training a typical map-reduce architecture (in OS classes) and I'm free to decide how the master process will tell its N child processes to parse a log. 我正在训练一个典型的map-reduce体系结构(在OS类中),我可以自由决定主进程如何告诉其N个子进程解析日志。 So, I'm kind of stuck in these two possibilities: 因此,我有点陷入这两种可能性:

  1. count the number of rows and give X rows for each map OR 计算行数并为每个地图提供X行,或者

  2. each map reads the line of its ID and the next line to read= current_one+number_of_existent_maps Eg: with 3 maps, each one is going to read these lines: 每张地图读取其ID行,下一行读取= current_one + number_of_existent_maps例如:对于3张地图,每张地图都将读取以下行:

    • Map1: 1, 4, 7, 10, 13 地图1:1,4,7,10,13
    • Map2: 2, 5, 8, 11, 14 地图2:2、5、8、11、14
    • Map3: 3, 6, 9, 12, 15 地图3:3、6、9、12、15

I have to do this in order to out-perform a single process that parses the entire log file, so the way I split the job between child processes has to be consistent with this objective. 我必须这样做才能胜过解析整个日志文件的单个进程,因此我在子进程之间划分工作的方式必须与此目标保持一致。

Which one do you think is best? 您认为哪一个最好? How can I do the scanf or fgets to adapt to 1) or 2)? 如何使scanf或fget适应1)或2)?

I would be happy with some example code for 2), because the fork/pipes are not my problem :P 我会为2)的一些示例代码感到满意,因为fork / pipes不是我的问题:P

RE-EDIT: I'm not encouraged to use select here, only between map procs and the reduce process that will be monitoring the reads. 重新编辑:不建议在此处仅在映射进程和将监视读取的缩小过程之间使用select。 I have restrictions now and : 我现在有限制,并且:

I want each process to read total_lines/N lines each. 我希望每个进程都读取total_lines / N行。 But it seems like I have to make map procs open the file and then read the respective lines. 但似乎我必须使map proc打开文件,然后读取相应的行。 So here are my doubts: 所以这是我的疑问:

1- Is it bad or even possible to make every procs open the file simultaneously or almost simultaneously? 1-使每个进程同时或几乎同时打开文件是否不好甚至有可能? How will that help in speeding up? 这将如何加快速度?

2- If it isn't possible to do that, I will have a parent opening the file (instead of each child doing that)that sends a struct with min and max limit and then the map procs will read whatever the lines they are responsible for, process them and give the reduce process a result (this doesn't matter for the problem now). 2-如果不可能做到这一点,我将有一个父级(而不是每个子级)打开文件,该父级发送带有最小和最大限制的结构,然后映射过程将读取其负责的任何行为此,对它们进行处理,然后给化简处理一个结果(现在这对问题不重要了)。

How can I divide correctly the number of lines by N maps and putting them to read at the same time? 如何正确地将行数除以N个图并同时读取? I think fseek() may be a good weapon, but I don't know HOW I can use it. 我认为fseek()可能是一个很好的武器,但是我不知道如何使用它。 Help, please! 请帮助!

If I understood correctly, you want to have all processes reading lines from a single file. 如果我理解正确,则希望所有进程都从单个文件读取行。 I don't recommend this, it's kinda messy, and you'll have to a) read the same parts of the file several times or b) use locking/mutex or some other mechanism to avoid that. 我不建议这样做,这有点混乱,您必须a)多次读取文件的相同部分,或者b)使用锁定/互斥量或其他某种机制来避免这种情况。 It'll get complicated and hard to debug. 它将变得复杂且难以调试。

I'd have a master process read the file, and assign lines to a subprocess pool. 我会让主进程读取文件,然后将行分配给子进程池。 You can use shared memory to speed this up, and reduce the need for data-copying IPC; 您可以使用共享内存来加快速度,并减少对数据复制IPC的需求。 or use threads. 或使用线程。

As for examples, I answered a question about forking and IPC and gave a code snippet on an example function that forks and returns a pair of pipes for parent-child communication. 作为示例,我回答了有关分叉和IPC的问题,并给出了一个示例函数的代码段,该函数分叉并返回一对用于父子通信的管道。 Let me look that up (...) here it is =P Can popen() make bidirectional pipes like pipe() + fork()? 让我查找(...),这里是= P popen()可以使像pipe()+ fork()的双向管道吗?

edit : I kept thinking about this =P. 编辑 :我一直在想这个=P。 Here's an idea: 这是一个主意:

  • Have a master process spawn subprocesses with something similar to what I showed in the link above. 有一个主流程产生子流程,其子内容与我在上面的链接中显示的内容类似。

  • Each process starts by sending a byte up to the master to signal it's ready, and blocking on read() . 每个进程都从向主机发送一个字节开始以表明已准备好,然后在read()上阻塞开始。

  • Have the master process read a line from the file to a shared memory buffer, and block on select() on its children pipes. 让主进程从文件读取一行到共享内存缓冲区,并在其子管道的select()上进行阻塞。

  • When select() returns, read one of the bytes that signal readiness and send to that subprocess the offset of the line in the shared memory space. select()返回时,读取表示准备就绪的字节之一,并将该行在共享内存空间中的偏移量发送给该子进程。

  • The master process repeats (reads a line, blocks on select, reads a byte to consume the readiness event, etc.) 主进程重复(读取一行,在选择时阻塞,读取一个字节以消耗就绪事件,等等)

  • The children process the line in whatever way you need, then send a byte to the master to signal readiness once again. 子进程以您需要的任何方式处理该行,然后将一个字节发送给主机,以再次发出就绪信号。

(You can avoid the shared memory buffer if you want, and send the lines down the pipes, though it'll involve constant data-copying. If the processing of each line is computationally expensive, it won't really make a difference; but if the lines require little processing, it may slow you down). (您可以根据需要避免使用共享内存缓冲区,并将这些行发送到管道中,尽管这将涉及不断的数据复制。如果每行的处理在计算上都非常昂贵,那实际上并不会有所作为;但是如果生产线需要很少的处理,则可能会使您减速。

I hope this helps! 我希望这有帮助!

edit 2 based on Newba's comments: 根据Newba的评论编辑2:

Okay, so no shared memory. 好的,所以没有共享内存。 Use the above model, only instead of sending down the pipe the offset of the line read in the shared memory space, send the whole line. 使用上面的模型,仅发送整个行,而不是向下发送管道在共享内存空间中读取的行的偏移​​量。 This may sound to you like you're wasting time when you could just read it from the file, but trust me, you're not. 这听起来像是您在浪费时间,您可以只从文件中读取它,但请相信我,您不是。 Pipes are orders of magnitude faster than reads from regular files in a hard disk, and if you wanted subprocesses to read directly from the file, you'll run into the problem I pointed at the start of the answer. 管道比从硬盘中的常规文件读取要快几个数量级,并且如果您希望子进程直接从文件中读取,则会遇到我在答案开头指出的问题。

So, master process: 因此,掌握流程:

  • Spawn subprocesses using something like the function I wrote (link above) that creates pipes for bidirectional communication. 使用类似于我编写的函数(上面的链接)的东西生成子进程,该函数创建用于双向通信的管道。

  • Read a line from the file into a buffer (private, local, no shared memory whatsoever). 将文件中的一行读入缓冲区(私有,本地,无共享内存)。

  • You now have data ready to be processed. 现在,您已准备好要处理的数据。 Call select() to block on all the pipes that communicate you with your subprocesses. 调用select()阻止与您的子流程进行通信的所有管道。

  • Choose any of the pipes that have data available, read one byte from it, and then send the line you have waiting to be processed in the buffer down the corresponding pipe (remember, we had 2 per child process, on to go up, one to go down). 选择任何有可用数据的管道,从中读取一个字节,然后将缓冲区中等待处理的行发送到对应管道的下方(请记住,每个子进程我们有2个,继续进行,一个往下走)。

  • Repeat from step 2, ie read another line. 从步骤2重复,即读取另一行。

Child processes: 子进程:

  • When they start, they have a reading pipe and a writing pipe at their disposal. 当他们开始时,他们有一个阅读管和一个书写管可供使用。 Send a byte down your writing pipe to signal the master process you are ready and waiting for data to process (this is the single byte we read in step 4 above). 在写入管道中发送一个字节,以向主进程发送信号,表明您已准备就绪,正在等待数据处理(这是我们在上面的第4步中读取的单个字节)。

  • Block on read() , waiting for the master process (that knows you are ready because of step 1) to send you data to process. read()上阻塞,等待主进程(由于步骤1知道您已准备就绪)将数据发送给进程。 Keep reading until you reach a newline (you said you were reading lines, right?). 继续阅读,直到到达换行符(您说您正在阅读行,对吧?)。 Note I'm following your model, sending a single line to each process at a time, you could send multiple lines if you wanted. 注意我遵循您的模型,一次向每个进程发送一行,如果需要,您可以发送多行。

  • Process the data. 处理数据。

  • Return to step 1, ie send another byte to signal you are ready for more data. 返回第1步,即发送另一个字节以表明您已准备好获取更多数据。

There you go, simple protocol to assign tasks to as many subprocesses as you want. 您可以通过简单的协议将任务分配给所需的多个子流程。 It may be interesting to run a test with 1 child, n children (where n is the number of cores in your computer) and more than n children, and compare performances. 对一个孩子,n个孩子(其中n是您计算机的内核数)和n个以上的孩子进行测试,然后比较性能。

Whew, that was a long answer. ew,那是一个很长的答案。 I really hope I helped xD 我真的希望我能帮助xD

Since each of the processes is going to have to read the file in its entirety (unless the log lines are all of the same length, which is unusual), there really isn't a benefit to your proposal 2. 由于每个过程都必须完整读取文件(除非日志行的长度都相同,这是不寻常的),所以建议2确实没有好处。

If you are going to split up the work into 3, then I would do: 如果您要将工作分解为3个,那么我会这样做:

  • Measure (stat()) the size of the log file - call it N bytes. 测量(stat())日志文件的大小-称它为N个字节。
  • Allocate the range of bytes 0..(N/3) to first child. 将字节范围0 ..(N / 3)分配给第一个孩子。
  • Allocate the range of bytes (N/3)+1..2(N/3) to the second child. 将字节范围(N / 3)+ 1..2(N / 3)分配给第二个子级。
  • Allocate the range of bytes 2(N/3)+1..end to the third child. 将字节2(N / 3)+ 1..end的范围分配给第三个孩子。
  • Define that the second and third children must synchronize by reading forward to the first line break after their start position. 定义第二个和第三个子代必须同步,方法是向前读取它们起始位置后的第一个换行符。
  • Define that each child is responsible for reading to the first line break on or after the end of their range. 定义每个孩子负责阅读范围结束时或结束后的第一个换行符。
  • Note that the third child (last child) may have to do more work if the log file is growing. 请注意,如果日志文件正在增长,则第三个孩子(最后一个孩子)可能需要做更多的工作。

Then the processes are reading independent segments of the file. 然后,进程将读取文件的独立段。

(Of course, with them all sharing the file, then the system buffer pool saves rereading the disk, but the data is still copied to each of the three processes, only to have each process throw away 2/3 of what was copied as someone else's job.) (当然,由于它们全部共享文件,因此系统缓冲池保存了重新读取磁盘的操作,但是数据仍被复制到三个进程中的每个进程中,而只是让每个进程丢弃了作为某人复制的内容的2/3其他人的工作。)


Another, more radical option: 另一个更彻底的选择:

  • mmap() the log file into memory. mmap()将日志文件存入内存。
  • Assign the children to different segments of the file along the lines described previously. 按照前面所述将子级分配到文件的不同段。

If you're on a 64-bit machine, this works pretty well. 如果您使用的是64位计算机,则效果很好。 If your log files are not too massive (say 1 GB or less), you can do it on a 32-bit machine too. 如果您的日志文件不是太大(例如1 GB或更小),您也可以在32位计算机上执行。 As the file size grows above 1 GB or so, you may start running into memory mapping and allocation issues, though you might well get away with it until you reach a size somewhat less than 4 GB (on a 32-bit machine). 随着文件大小增加到超过1 GB左右,您可能会开始遇到内存映射和分配问题,尽管您可以完全避免使用它,直到达到小于4 GB的大小(在32位计算机上)。 The other issue here is with growing log files. 另一个问题是日志文件的增长。 AFAIK, mmap() doesn't map extra memory as extra data is written to the file. AFAIK,由于将额外的数据写入文件,因此mmap()不会映射额外的内存。

Use a master and slave queue pattern. 使用主队列和从属队列模式。

  • The master sets up the slaves which sit waiting on a queue for work items. 主机设置从机,从机坐在队列中等待工作项。
  • The master then reads the file line by line. 然后,主机会逐行读取文件。
    • Each line then represents a work item that you put on the queue 然后,每一行代表您放入队列的工作项
      with a function pointer of how do the work. 与如何工作的函数指针。
    • One of the waiting slaves then takes the item of the queue 然后,一个等待的奴隶拿走队列中的项目
    • A slave processes a work item. 从站处理工作项。
    • When a slave has finished it rejoins the work queue. 从站完成后,它会重新加入工作队列。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM