table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1rem;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
pre {
background-color: #f8f8f8;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
}
1、如何对文件复制程序进行性能测试?请描述具体步骤。
具体步骤为:
使用大文件进行测试,可通过
dd
等方法生成大文件。
用
time
工具记录文件复制程序的执行时间。
尽可能获取多种组合的数据并比较结果,保持其他系统参数不变以得到有效对比。
建议将结果制成电子表格方便分析。
2、请使用你能使用的计算机上的测试程序来研究文件复制和转换的性能。此外,若条件允许,使用网络文件系统、存储区域网络(SAN)等来进行性能测试,以了解在进行顺序文件访问时各种存储架构的影响。
研究任务:文件复制与转换性能及存储架构影响
该内容是一个研究任务,旨在通过使用测试程序和不同存储架构,研究文件复制和转换的性能以及存储架构对顺序文件访问的影响。
研究步骤
利用能使用的计算机上的测试程序进行文件复制和转换的性能测试。
若条件允许,使用网络文件系统、存储区域网络(SAN)等进行性能测试。
分析测试结果,了解不同存储架构在顺序文件访问时对性能的影响。
3、使用某函数来确定不同 Windows 版本如何稀疏地分配文件空间。操作步骤为:创建一个新文件,将文件指针设置为一个较大的值,设置文件大小,使用该函数调查可用空间。该函数同样可用于确定磁盘如何配置为扇区和簇,还需确定新分配的文件空间是否已初始化。示例文件中提供了该函数作为解决方案。请比较 NT5 和 NT6 的结果,并研究如何使文件成为稀疏文件。
可按以下步骤操作:
创建新文件
将文件指针设为较大值
设置文件大小
用该函数调查可用空间
用此函数确定磁盘扇区和簇的配置
判断新分配文件空间是否初始化
示例文件中的函数是解决方案
比较 NT5 和 NT6 的结果
还可研究使文件成为稀疏文件的方法
4、编写一个与同名 UNIX 命令类似的命令,该命令可以移动一个完整的目录。一个重要的考虑因素是目标目录是否与源文件或目录位于不同的驱动器上。如果是,则复制文件;否则,移动目录。
要编写这样的命令,需要判断目标是否和源文件或目录在不同驱动器。若在不同驱动器,编写复制文件的代码;若在同一驱动器,编写移动目录的代码。结合条件判断逻辑实现功能。
5、增强一个程序,使新的文件时间可以在命令行中指定。UNIX命令允许时间戳(可选)出现在常规选项之后、文件名之前。时间格式为[ ,其中大写 表示月份, 表示分钟。请说明如何对程序进行改进。
需对程序进行改进,让其支持在命令行指定新文件时间。时间格式按
[
设定,大写
M
代表月份,
m
代表分钟,且时间戳可(非必须)置于常规选项后、文件名前。
6、为处理大型NTFS文件系统编写了程序3 – 1,若有足够可用磁盘空间,需用长度大于4GB(可能的话更大)的巨大文件来测试该程序,还要参考运行3 – 2验证64位算术运算是否正确。不建议在未经网络管理员许可的情况下在网络驱动器上进行此练习,完成后要删除测试文件。请简述此操作的主要内容。
该操作主要是指导对程序3-1用大文件进行测试,验证64位算术运算,同时给出了在网络驱动器操作的注意事项以及完成测试后要删除测试文件的提醒。
7、编写一个程序,对指定文件加锁并长时间持有该锁。在持有锁期间,尝试用编辑器访问该文件(使用文本文件)。会发生什么?文件是否被正确锁定?或者,编写一个程序,提示用户对测试文件指定锁。可以在不同窗口中运行该程序的两个实例,以验证文件锁定是否按描述工作。
文件锁定程序编写与测试步骤
要编写程序来实现文件锁定并测试其效果,可按以下步骤操作:
编写文件锁定程序
:使用合适的编程语言和文件锁定相关函数,对指定文件加锁并长时间持有该锁。
测试文件访问
:在持有锁期间,用编辑器尝试访问该文件,观察是否能正常访问。若不能正常访问,说明文件被正确锁定。
多实例验证
:编写程序提示用户对测试文件指定锁,在不同窗口运行该程序的两个实例,验证文件锁定是否按描述工作。
8、不同版本的 Windows 在堆的内存开销方面存在显著差异。设计并进行一个实验,以测量每个系统在单个堆中能提供多少个固定大小的块。使用结构化异常处理(SEH)来检测何时所有块都已分配,这会使程序更易于实现。
可按照以下步骤设计并开展实验:
编写程序,利用 SEH 机制来检测内存分配失败的情况,当出现异常时,意味着所有块已分配。
定义固定大小的块,在程序中循环分配这些固定大小的块到单个堆中。
对不同版本的 Windows 系统运行该程序,记录每个系统中成功分配的固定大小块的数量。
9、创建一个程序,该程序分配一个足够大的内存缓冲区来容纳文件,并将文件读入该缓冲区,不进行内存映射。同时编写一个使用内存映射读取文件的程序,比较这两个程序的性能。
可以通过以下步骤完成:
首先,在程序中分配一个足够大的内存缓冲区以容纳文件。
接着,将文件内容读取到该缓冲区中,此过程不使用内存映射。
然后,编写一个使用内存映射读取文件的程序。
最后,比较这两个程序的性能。
通常,不使用映射的方法可能和使用内存映射的方法一样有效,且往往更快。
10、如果编译器不支持特定于Microsoft C的指针特性,用宏、数组或其他机制重新实现程序以生成基指针值。
可尝试使用宏、数组或其他合适机制来替代特定于 Microsoft C 的指针,重新实现程序以生成基指针值,但具体实现需结合程序本身逻辑进行编写代码。
11、修改程序 5 – 7,使选择使用哪个 DLL 的决策基于文件大小和系统配置。该文件不是必需的,因此要找出如何抑制该文件的生成。使用某个工具来确定文件系统类型。为转换函数创建额外的 DLL,每个版本使用不同的文件处理技术,并扩展调用程序以决定何时使用每个版本。
可以先在程序中获取文件大小和系统配置信息,根据这些信息来决定使用哪个 DLL。对于抑制文件生成,可检查程序中生成文件的代码部分并进行修改。利用指定工具确定文件系统类型。创建新的 DLL,在其中实现不同的文件处理技术,在调用程序中添加逻辑,根据文件大小、系统配置和文件系统类型等信息来选择合适的 DLL 版本。
12、将一些实用函数和命令行选项及参数处理函数放入一个动态链接库(DLL),并重新构建一些早期的程序。具体是把函数A、函数B、函数C和函数D实用函数放入DLL,对函数E和函数F(命令行选项和参数处理函数)也进行同样的操作。重要的是,实用DLL和调用程序都要以DLL形式使用C库。例如,在Visual Studio中,可以在项目设置里选择“使用运行时库(多线程 DLL)”。注意,一般来说,DLL必须是多线程的,因为它们会被多个进程的线程使用。
要完成此任务,可按以下步骤操作:
创建DLL项目,将以下内容添加到该项目中进行编译,生成DLL文件:
– 函数A
– 函数B
– 函数C
– 函数D 实用函数
– 函数E
– 函数F 命令行选项和参数处理函数
重新构建早期程序,让这些程序调用新生成的DLL。
在Visual Studio项目设置中选择“使用运行时库(多线程 DLL)”,确保实用DLL和调用程序都以DLL形式使用C库。
由于DLL会被多个进程的线程使用,编写DLL代码时要确保其是多线程安全的。
13、编写一个程序,该程序会启动多个进程,在列出结果之前等待所有进程完成。在当前程序中,无法确定进程实际完成的顺序。修改该程序,使其也能确定进程终止的顺序。提示:修改调用,使其在每个单独的进程终止后返回。另一种方法是按进程终止时间排序。
可按以下两种方法修改程序以确定进程终止顺序:
修改调用
:让其在每个单独的进程终止后返回;
按进程终止时间进行排序
。
14、确定在不同多处理器系统上,或者当文件位于独立或网络驱动器上时,并行执行与顺序执行相比的性能优势。
运行6 - 1中,并行执行比四个顺序执行快四倍多(实时时间为15秒,而顺序执行是77秒),多进程即便有进程创建开销,仍获得了超出预期的性能提升。在多处理器环境下,程序中的进程和线程可在多处理器计算机的不同处理器上并发运行,能大幅提升程序执行的耗时表现,且无需显式的程序操作来实现性能改进。
不过,性能提升并非与处理器数量呈线性关系,因为存在开销成本以及需要顺序输出结果的情况。在多处理器系统中,当线程数量与处理器数量相同时效果较好,线程增多性能会提升但因合并操作并非线性提升,超过处理器数量的额外线程会使程序变慢。
在内存有限的单处理器计算机上,多线程可能因争夺物理内存而增加排序时间;而内存充足时,多线程能提升单处理器的性能,且结果还与初始数据排列密切相关。
15、作业管理功能存在一个难以修复的缺陷。当一个作业被终止,且在作业管理文件中删除该进程ID之前,执行程序重新使用了该进程ID,此时进程ID可能指向一个完全不同的进程。请说明修复该缺陷的方法,并比较UNIX和Windows在进程ID重用机制上的差异,以及对Windows程序有什么提醒。
修复该缺陷的方法有两种:
是创建一个辅助进程,为每个创建的进程持有复制的句柄,使进程ID不被重用;
二是在作业管理文件中包含进程启动时间,且该时间应与从进程ID获取的进程启动时间相同。
UNIX通过递增一个计数器来获取新的进程ID,只有在32位计数器溢出后,ID才会重复;而Windows中进程ID会很快被重用。提醒Windows程序不能假定进程ID不会被重用。
16、增强程序,使命令能包含每个作业正在使用的句柄数量统计。
在程序中添加相应代码来统计每个作业使用的句柄数量,并将其包含在命令输出中。
17、程序存在一些异常和缺陷。运行6 – 7显示,前两个作业启动后共有7个进程,且均为活跃状态,判断这个数值是否正确。作业终止后,有10个进程且均为非活跃状态,判断这是程序中的bug、Windows中的bug,还是这个数值是正确的。程序6 – 7显示的用户时间结果以秒为单位看似合理,判断是否认同。它通过将总用户时间除以1,000,000来得到这些结果,意味着时间是以微秒为单位返回的,然而MSDN称时间是以100纳秒为单位的,所以应该除以10,000,000,调查是MSDN错了吗。进程时间限制是否真正起作用,程序实现是否正确,可使用程序5 – 1进行实验(程序5 – 1是一个可用于实验的耗时程序)。
对于运行6 – 7中进程数量及活跃状态的情况,需进一步分析程序逻辑与Windows系统机制来判断数值是否正确以及是否存在bug;
关于用户时间计算,需进一步测试验证是MSDN错误还是程序计算有误;
对于进程时间限制,可使用程序5 – 1进行实验来确定是否起作用以及程序实现是否正确。
18、示例文件中包含一个多线程单词计数程序,其结构与另一个程序类似,同时还包含一个有缺陷的版本。在不参考正确解决方案的情况下,分析并修复该有缺陷版本中的缺陷,包括任何语法错误。此外,创建能说明这些缺陷的测试用例,并进行与另一个程序类似的性能实验。若使用 Cygwin(开源的 UNIX/Linux 命令和 shell),比较 Cygwin 版本与该程序在多处理器系统上的性能。
程序缺陷分析与性能测试流程
缺陷分析与修复
– 对存在缺陷的程序版本进行缺陷分析
– 识别并修复其中的语法错误
测试用例设计
– 设计多种测试用例
– 验证程序在不同场景下的缺陷表现
性能实验
– 开展类似的性能实验
– 记录相关的性能数据
程序运行与性能比较
– 使用 Cygwin 运行对应程序
– 在多处理器系统上比较不同程序的性能
– 分析两者之间的性能差异
19、程序在创建排序线程之前,会在主线程中读取整个文件。修改程序,让每个线程读取其所需的文件部分。接着,修改程序以使用映射文件。
由于没有提供具体程序代码,以下是实现这两个修改的通用思路。
首先,要让每个线程读取其所需的文件部分,需要确定每个线程负责的文件范围,之后在每个线程函数里使用文件操作函数读取对应范围的数据。
其次,要修改程序以使用映射文件,需要使用操作系统提供的文件映射函数,将文件映射到内存,然后每个线程可以直接访问内存中的文件数据。
以下是伪代码示例:
import threading
import mmap
# 假设文件名为 'example.txt'
file_name = 'example.txt'
# 定义每个线程负责的文件范围
chunk_size = 1024 # 每个线程处理的字节数
# 读取文件大小
with open(file_name, 'r') as f:
file_size = len(f.read())
# 计算线程数量
num_threads = file_size // chunk_size
if file_size % chunk_size != 0:
num_threads += 1
# 线程函数,每个线程读取其所需的文件部分
# 方法一:普通文件读取
# def read_chunk(thread_id):
# start = thread_id * chunk_size
# end = min(start + chunk_size, file_size)
# with open(file_name, 'r') as f:
# f.seek(start)
# data = f.read(end - start)
# # 在这里进行排序或其他处理
# print(f'Thread {thread_id} read {len(data)} bytes')
# 方法二:使用映射文件
# 定义线程函数
# def read_mapped_chunk(thread_id):
# start = thread_id * chunk_size
# end = min(start + chunk_size, file_size)
# with open(file_name, 'r+b') as f:
# # 创建文件映射
# mm = mmap.mmap(f.fileno(), 0)
# data = mm[start:end]
# # 在这里进行排序或其他处理
# print(f'Thread {thread_id} read {len(data)} bytes from mapped file')
# mm.close()
# 创建并启动线程
threads = []
for i in range(num_threads):
# 选择方法一或方法二
# t = threading.Thread(target=read_chunk, args=(i,))
# t = threading.Thread(target=read_mapped_chunk, args=(i,))
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
这段伪代码展示了如何让每个线程读取其所需的文件部分,以及如何使用映射文件。需要根据具体的编程语言和排序需求进行调整。
20、创建一个工作线程池,并控制工作线程的运行速率,要求在任何 1 秒间隔内只允许一个线程运行。之后修改程序,使得在 1 秒间隔内可以有两个线程运行,同时线程的整体运行速率限制为每秒一个。提示:工作线程应等待一个事件,并且一个控制线程应每秒发出一次该事件的信号。
可使用手动重置事件,因为它能让多个线程在被信号唤醒后继续执行。控制线程每秒发出一次信号,若要开始时只允许一个线程运行,可在信号发出后立即重置事件;若要在 1 秒间隔内允许两个线程运行,可在第一个线程被唤醒后,不立即重置事件,让第二个线程也能被唤醒,之后再重置事件以限制整体速率为每秒一个。
21、通过修改工作函数中的延迟时间来对这些变体进行实验。若增加延迟,所有变体的总耗时和锁模型的相对影响会发生怎样的变化?
增加延迟会使所有变体总耗时增加,不过锁模型的相对影响会变小。
22、使用信号量节流阀来限制运行线程的数量,在单处理器和多处理器系统上对该限制的线程数量进行实验。若有NT4系统,将该系统的实验结果与NT5和NT6系统的实验结果进行比较。
要进行此实验,可按以下步骤操作:
在单处理器和多处理器系统上,使用信号量节流阀限制运行线程数量。先设置信号量的最大和初始值,可将其设为处理器数量等参数,之后通过实验调整到最佳值。
每个工作线程在进入临界代码段前等待信号量,离开后释放信号量。
若有NT4系统,在该系统上进行同样实验,记录运行结果。
将NT4系统的结果与NT5和NT6系统的结果进行对比,分析不同系统中信号量节流阀对线程数量限制的效果及性能差异。
23、通过修改程序,将处理器亲和性作为一种可能的性能增强技术加以运用,应该怎么做?
可以从以下方面利用处理器亲和性来增强程序性能:
专用处理器
:将一个或多个线程分配给特定处理器,排除其他线程。不过要控制所有运行的应用程序,且 Windows 仍可能调度自身线程。
利用缓存
:将线程集合分配给共享 L2 缓存的处理器对,以减少内存屏障导致的延迟。
资源竞争处理
:将竞争单一资源的工作线程分配到同一个处理器。
测试处理器
:也可用于测试处理器。
在修改程序时,可通过设置系统、进程和线程亲和性掩码来实现上述操作。每个进程有自己的进程亲和性掩码(位向量),系统也有系统亲和性掩码。线程亲和性掩码必须是进程亲和性掩码的子集,通过调整这些掩码来分配线程到特定处理器。
同时,要注意性能方面可能存在因内存缓存架构和多核心芯片组合带来的微妙问题。此外,避免仅凭理论推测性能,要通过简单原型或应用程序的替代实现进行测试。
24、修改一个程序,使其支持多个消费者,并使用条件变量模型。哪种事件类型合适?
使用手动重置事件(广播模型)比较合适,这样当队列非空或非满时,可以通知多个线程。
25、程序使用广播条件变量(CV)模型来指示队列是不空还是不满。信号CV模型是否可行?信号模型在某些方面是否更可取?
信号CV模型可行。在某些情况下信号模型更可取,如在性能测量中,信号模型能随线程数量扩展,而广播模型不能很好扩展,尤其在32和64个线程时;且广播模型会导致大量系统CPU时间消耗,而信号模型无此问题。
26、研究使用临界区(CS)而非互斥量(mutex)时程序的性能表现。
在信号模型中,临界区(CS)与条件变量相比表现良好,尽管临界区实现需要超时设置。在不同线程数量的测试里,临界区版本比使用互锁函数的版本明显慢2倍或更多,而互斥量版本执行时间可能比临界区版本长2到30倍。在NT5之前,当线程数超过4时,临界区性能并非总能随线程数量扩展,NT5对此有显著改进。
27、阅读施密特(Schmidt)和皮亚拉利(Pyarali)所著的《在 Win32 中实现 POSIX 条件变量的策略》。将他们关于公平性、正确性、序列化等方面的分析应用到一个模拟正常条件变量使用的模型中。要注意,该模型解决的是相对简单的模拟正常条件变量使用问题,而施密特和皮亚拉利模拟的是在任意上下文中使用的条件变量。
需先阅读指定文章,再将文章中关于
公平性
、
正确性
、
序列化
等方面的分析应用到模拟正常条件变量使用的模型中,同时注意该模型与文章在模拟条件变量上的差异。
28、修改使用条件变量实现消息队列管理函数的程序,使其使用 SRW(轻量级读写)锁。使用合适的方法进行测试,并将性能与原始实现进行比较。进一步修改实现以使用线程池。
任务说明
先对使用条件变量实现消息队列管理函数的程序进行修改,用 SRW 锁替代原有的锁机制。
然后使用合适的方法进行测试,记录并对比使用 SRW 锁和原始实现的性能表现。
最后,再次修改程序,引入线程池机制。
29、启动服务器和多个客户端,每个客户端使用“分离”选项创建。最后,通过命令发送控制台控制事件来关闭服务器。你能对关闭逻辑提出改进建议,使连接的服务器线程在阻塞等待客户端请求时可以测试关闭标志吗?提示:创建一个类似于连接线程的读取线程。
可以创建一个类似于连接线程的
读取线程
。此读取线程可以在阻塞等待客户端请求时,
周期性地测试全局关闭标志
。这样,即使服务器线程在等待客户端请求时被阻塞,也能通过读取线程来检查关闭标志,从而实现服务器的
正常关闭
。
30、修改服务器,以便测量服务器的利用率(即,在服务器中花费的时间占总时间的百分比是多少)。维护性能信息,并应客户端请求将此信息报告给客户端。考虑使用该字段来保存信息。
可通过记录服务器开始运行时间和结束运行时间,计算出总时间。
在服务器运行过程中,记录服务器实际处理任务的时间。
将处理任务时间除以总时间,即可得到服务器利用率。
使用一个字段保存这些性能信息,当客户端请求时,将信息发送给客户端。
31、该程序被设计为作为服务器无限期运行,允许客户端连接、获取服务并断开连接。当客户端断开连接时,服务器释放所有相关资源(如内存、文件句柄和线程句柄)非常重要。任何残留的资源泄漏最终会耗尽计算机资源,导致服务器故障,并且在故障发生前可能会出现显著的性能下降。仔细检查该程序,确保没有资源泄漏,如果发现有泄漏,请修复它们。(另外,请使用前言中的电子邮件地址通知作者)。注意:资源泄漏是许多生产系统中常见且严重的缺陷。如果没有解决这个问题,任何“行业级”的质量保证工作都是不完整的。
需要仔细检查程序,确保客户端断开连接时服务器能释放所有相关资源,避免资源泄漏。若发现泄漏需修复,并通过前言中的邮箱地址告知作者。
32、拓展练习:同步对象通常只能同步同一机器上不同进程中的线程,无法同步不同机器上进程中运行的线程。请说明如何使用命名管道和邮件槽创建模拟的互斥量、事件和信号量来克服这一限制。
此为一个拓展练习任务,可按如下步骤实现:
理解互斥量、事件和信号量的基本原理。
– 互斥量用于保证同一时间只有一个线程可以访问共享资源;
– 事件用于线程间的通知;
– 信号量用于限制对共享资源的并发访问数量。
利用命名管道和邮件槽的特性来模拟这些同步对象。
–
对于模拟互斥量
,可使用命名管道或邮件槽来实现一个锁机制,通过消息传递来控制对资源的访问。
–
对于模拟事件
,可通过发送特定消息来通知其他线程。
–
对于模拟信号量
,可通过计数消息的数量来控制并发访问。
编写代码实现模拟逻辑。
根据上述原理,编写代码来实现模拟的互斥量、事件和信号量,并在不同机器上的进程中进行测试。
测试和优化。
在不同机器上的进程中测试模拟同步对象的功能,根据测试结果进行优化和调整。
33、修改客户端和服务器程序,使它们使用数据报来定位服务器。可以以邮件槽解决方案作为起点,邮件槽解决方案中服务器广播信息,客户端接收信息。
以下是大致的修改思路:
首先了解数据报的工作原理,它是一种无连接的传输方式。
在服务器端,将原本通过邮件槽广播自身信息(如名称、命名管道名)的部分,改为通过数据报发送这些信息。
在客户端,将原本从邮件槽接收服务器信息的操作,改为接收数据报。
同时要处理好数据报的发送和接收相关的错误情况、网络配置等问题。
具体实现时,需要根据使用的编程语言和相应的网络库来编写代码。
34、进行实验以确定进程内服务器是否比进程外服务器更快。例如,你可以设计一个字数统计程序,程序包含一个可执行程序以及对应的 DLL 函数。
要完成此实验,可设计一个字数统计程序,程序包含可执行程序和 DLL 函数。运行实验时,分别使用进程内服务器和进程外服务器来执行字数统计任务,记录并对比两者完成任务的时间,从而判断进程内服务器是否比进程外服务器更快。