我编写了一个简单的文件复制应用程序来衡量使用 sendfile API 相对于大文件的常规读取文件和写入套接字方法的有效性。但是,在使用这两种方法运行应用程序后,我发现完成文件复制所花费的时间在这两种方法之间的差异非常小。
我从多个来源了解到,“sendfile”API 将比普通的从文件读取和写入套接字的方法提供巨大的性能改进。但是,当我尝试使用单个 2GB 文件进行基准测试时,以下是我观察到的数字(4 次迭代的平均值):
- 正常的从文件读取和写入套接字的方法:17 秒 444840 usecs
- 发送文件 API:17 秒 431420 秒
我在隔离的 1Gbps 网络中的两台不同机器(Linux 内核版本 4.4.162-94.72-default)上运行应用程序的服务器和客户端部分。
有人能帮我看看这里到底做错了什么或遗漏了什么吗?
服务器:
#define _GNU_SOURCE
#include "file_details.h"
void calculate_execution_time(struct timeval start, struct timeval end)
{
struct timeval time_diff;
time_diff.tv_sec = end.tv_sec - start.tv_sec;
time_diff.tv_usec = end.tv_usec - start.tv_usec;
// Adjust the time appropriately
while (time_diff.tv_usec < 0) {
time_diff.tv_sec--;
time_diff.tv_usec += 1000000;
}
printf("total execution time: = %lds.%ldus\n", time_diff.tv_sec, time_diff.tv_usec);
}
int read_from_file_pread(int client_sockfd, char *file_name, int fd, off_t file_size_in_bytes, int chunk_size)
{
ssize_t bytes_read = 0, bytes_sent = 0, total_bytes_sent = 0, bytes_sent_this_itr = 0;
off_t offset = 0;
char *buffer = NULL;
struct timeval start_time, end_time;
buffer = calloc(chunk_size, sizeof(char));
if (buffer == NULL) {
printf("Failed to allocate memory of size: %d bytes\n", chunk_size);
return -1;
}
gettimeofday(&start_time, NULL);
do {
bytes_read = pread(fd, buffer, chunk_size, offset);
switch (bytes_read) {
case -1:
printf("Failed to read from file: %s, offset: %lu, error: %d\n", file_name, offset, errno);
free(buffer);
return -1;
case 0:
printf("Completed reading from file and sending\n");
break;
default:
do {
bytes_sent = send(client_sockfd, buffer, (bytes_read - bytes_sent_this_itr), 0);
if (bytes_sent == -1) {
printf("Failed to send %lu bytes, error: %d\n", (bytes_read - bytes_sent_this_itr), errno);
free(buffer);
return -1;
}
bytes_sent_this_itr += bytes_sent;
} while (bytes_sent_this_itr < bytes_read);
bytes_sent = 0;
bytes_sent_this_itr = 0;
offset += bytes_read;
total_bytes_sent += bytes_read;
break;
}
} while (total_bytes_sent < file_size_in_bytes);
gettimeofday(&end_time, NULL);
printf("File size: %lu bytes, total bytes read from file: %lu, ", file_size_in_bytes, total_bytes_sent);
calculate_execution_time(start_time, end_time);
free(buffer);
return 0;
}
int read_from_file_sendfile(int client_sockfd, char *file_name, int fd, off_t file_size_in_bytes, int chunk_size)
{
ssize_t bytes_sent = 0, total_bytes_sent = 0;
off_t offset = 0;
struct timeval start_time, end_time;
gettimeofday(&start_time, NULL);
do {
bytes_sent = sendfile(client_sockfd, fd, &offset, chunk_size);
if (bytes_sent == -1) {
printf("Failed to sendfile: %s, offset: %lu, error: %d\n", file_name, offset, errno);
return -1;
}
total_bytes_sent += bytes_sent;
} while (total_bytes_sent < file_size_in_bytes);
gettimeofday(&end_time, NULL);
printf("File size: %lu bytes, total bytes read from file: %lu, ", file_size_in_bytes, total_bytes_sent);
calculate_execution_time(start_time, end_time);
return 0;
}
int read_from_file(int client_sockfd, char *file_name, char *type, int chunk_size)
{
int error_code = 0, fd = 0;
ssize_t hdr_length = 0, bytes_sent = 0, file_name_length = strlen(file_name);
struct stat file_stat = {0};
struct file_details *file_details_to_send = NULL;
fd = open(file_name, O_RDONLY, S_IRUSR);
if (fd == -1) {
printf("Failed to open file: %s, error: %d\n", file_name, errno);
return -1;
}
error_code = fstat(fd, &file_stat);
if (error_code == -1) {
printf("Failed to get status of file: %s, error: %d\n", file_name, errno);
close(fd);
return -1;
}
hdr_length = (sizeof(struct file_details) + file_name_length + 1);
file_details_to_send = calloc(hdr_length, sizeof(char));
if (file_details_to_send == NULL) {
perror("Failed to allocate memory");
close(fd);
return -1;
}
file_details_to_send->file_name_length = file_name_length;
file_details_to_send->file_size_in_bytes = file_stat.st_size;
strcpy(file_details_to_send->file_name, file_name);
printf("File name: %s, size: %lu bytes\n", file_name, file_stat.st_size);
bytes_sent = send(client_sockfd, file_details_to_send, hdr_length, 0);
if (bytes_sent == -1) {
printf("Failed to send header of size: %lu bytes, error: %d\n", hdr_length, errno);
close(fd);
return -1;
}
if (strcmp(type, "rw") == 0) {
printf("By pread and send\n");
read_from_file_pread(client_sockfd, file_name, fd, file_stat.st_size, chunk_size);
} else {
printf("By sendfile\n");
read_from_file_sendfile(client_sockfd, file_name, fd, file_stat.st_size, chunk_size);
}
close(fd);
return 0;
}
int main(int argc, char *argv[])
{
...
...
option_value = 1;
error_code = setsockopt(client_sockfd, SOL_TCP, TCP_NODELAY, &option_value, sizeof(int));
if (error_code == -1) {
printf("Failed to set socket option TCP_NODELAY to socket descriptor: %d, error: %d", client_sockfd, errno);
}
read_from_file(client_sockfd, file_name, type, chunk_size);
...
}
最佳答案
您的代码几乎肯定会带来很大的性能提升。问题可能是您正在测量墙上时间。考虑调用 getrusage() 而不是 gettimeofday()。 ru_utime 和 ru_stime 字段表示内核和您的程序花在实际工作上的时间。 sendfile() 应该使这些数字下降。这样您就可以消耗更少的能源,并为计算机上的其他程序释放更多资源。不幸的是,它不能使网络运行得更快。假设开销为零,在 1GbPS 以太网上发送 2GB 的最佳墙时间速度约为 9 秒。你非常接近。
关于c - 在 Linux 上测量 sendfile API,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57006305/