Измерение API sendfile в Linux

Я написал простое приложение для копирования файлов, чтобы измерить эффективность использования sendfile API по сравнению с обычным подходом чтения из файла и записи в сокет для больших файлов. Однако, запустив приложение с использованием обоих подходов, я обнаружил, что разница во времени, необходимом для завершения копирования файла, очень минимальна между этими двумя подходами.

Я читал из нескольких источников, что API «sendfile» даст огромное улучшение производительности по сравнению с обычным подходом чтения из файла и записи в сокет. Но когда я попытался сравнить с одним 2-гигабайтным файлом, я получил следующие цифры (в среднем по 4 итерациям):

  1. Обычный подход чтения из файла и записи в сокет: 17 секунд 444840 мкс
  2. API sendfile: 17 секунд 431420 мкс

Я запускаю и серверную, и клиентскую части приложения на двух разных машинах (версия ядра Linux 4.4.162-94.72 по умолчанию) в изолированной сети 1 Гбит/с.

Может ли кто-нибудь помочь мне, что именно я здесь делаю неправильно или здесь отсутствует?

Сервер:

#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);

    ...
}

Как быстро прочитать файл и скопировать его в /dev/null?

Andrew Henle 12.07.2019 13:46

@AndrewHenle, скопируйте в /dev/null -- реальный 0m8.763s, пользователь 0m0.005s, sys 0m0.623s

Siva 13.07.2019 08:48

Копирование с использованием обычного чтения из файла, а затем записи в сокет -- реальный 0m19.568s, пользователь 0m0.013s, sys 0m1.011s

Siva 13.07.2019 08:50

Скопируйте с помощью sendfile -- реальный 0m18.431s, пользователь 0m0.001s, sys 0m0.279s

Siva 13.07.2019 08:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
281
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваш код почти наверняка значительно улучшил производительность. Проблема может заключаться в том, что вы измеряете время стены. Рассмотрите возможность вызова getrusage() вместо gettimeofday(). Поля ru_utime и ru_stime показывают, сколько времени ядро ​​и ваша программа потратили на реальную работу. sendfile() должен уменьшать эти числа. Таким образом, вы потребляете меньше энергии и освобождаете больше ресурсов для других программ на вашем компьютере. К сожалению, однако, это не может заставить сеть работать быстрее. Оптимальная временная скорость для отправки 2 ГБ по Ethernet 1 Гбит/с при нулевых накладных расходах будет ~ 9 с. Ты довольно близко.

Правильный. Фактическое время передачи зависит от пропускной способности сети и задержки больше, чем от чего-либо еще, и не зависит от того, какой API вы используете. Однако sendfile() работает в основном в ядре, экономя много шагов копирования, поэтому процессорное время намного дешевле.

user207421 12.07.2019 14:41

Большое спасибо Джастин Танни за быстрый ответ. Да, использование getrusage() или запуск программы с командой «time» показало преимущество использования sendfile с точки зрения использования ЦП (режим пользователя и режим ядра).

Siva 13.07.2019 08:33

Шива, для потомков, поделитесь мыслями о том, на какой процент, по вашему мнению, ушло процессорное время? Я люблю числа.

Justine Tunney 13.07.2019 08:48

Скопировать в /dev/null -- реальный 0m8.763s, пользовательский 0m0.005s, sys 0m0.623s; Копировать, используя обычное чтение из файла, а затем запись в сокет -- реальный 0m19.568s, пользователь 0m0.013s, sys 0m1.011s; Скопируйте с помощью sendfile -- реальный 0m18.431s, пользователь 0m0.001s, sys 0m0.279s

Siva 13.07.2019 08:55

Другие вопросы по теме