Python 打印大字符串实际上可以比 C++ 更快吗?

问题描述 投票:0回答:1

我是一名刚接触 C++ 的学生,我想编写一些代码来习惯这种语言。不久前,我编写了一个将 ASCII 艺术制作为“视频”的 Python 程序。基本上,它将视频的每一帧转换为存储在列表中的字符串,然后将它们显示在终端上。但计算速度太慢,所以我决定改用 C++。最后,我通过切换到 C++ 成功地加快了转换过程。

但是,当我尝试在终端上打印 ASCII 帧时,我注意到出现了一个奇怪的卡顿问题,而 Python 则不会出现这种问题(请参阅链接): 视频链接

这是处理打印的 C++ 代码片段。

(...)

    auto start_time(chrono::high_resolution_clock::now());
    int current_index(0);
    int last_index(0);

    while (current_index < number_of_frames - 1) {
        // Computing the current index
        double elapsed_time(chrono::duration_cast<chrono::milliseconds>(chrono::high_resolution_clock::now() - start_time).count() / 1000.);
        double percentage(elapsed_time / audio_duration);
        current_index = percentage * number_of_frames;

        // Print the new frame if it's different from the last one to avoid spamming them
        if (current_index != last_index) {
            _write(_fileno(stdout), frames[current_index].c_str(), frames[current_index].size());
        }
        last_index = current_index;
    }

(...)

为了与Python进行比较,这里是该代码的Python版本。它使用

time
sys
模块。

(...)

start_time = time.time()

current_frame = 0  # nombre de frames passées
last_frame = 0

while current_frame < len(frames) - 1:

    elapsed_time = time.time() - start_time
    percentage = (elapsed_time / float(audio_duration))
    current_frame = int(percentage * number_of_frames)

    if current_frame != last_frame:
        sys.stdout.write(frames[current_frame])
    last_frame = current_frame

(...)

虽然这似乎是一个非常具体的问题,但在这种情况下,Python 比 C++ 工作得更好是没有任何意义的。谷歌也没有帮助,所以我的源代码中一定有问题。

我尝试调用不同类型的函数,如

std::cout
std::prinf
和原始
write
,甚至对帧数据进行了一些修改,例如调整其中一些数据的大小,但无济于事。我真的不知道是什么导致了这个问题。

以下是在 C++ 中重现该问题的一些步骤(伪代码/概要):

  1. 创建一个字符串向量。字符串的长度必须为
    terminal_height*terminal_width
    才能覆盖整个屏幕。它可以是任何东西,但我建议使用易于识别的模式,例如 ASCII 艺术。
  2. 迭代该向量的字符串并将其打印在终端上。这里的打印速度似乎并不重要。我测试了每秒打印 15 -> 30 个字符串的速率,结果似乎是相同的。
  3. 将终端设置为全屏并运行程序。

这是完整的 C++ 代码。它使用 OpenCV 和 FFmpeg。我还没有尝试优化任何东西。

#include <iostream>
#include <filesystem>
#include <Windows.h>
#include <fstream>
#include <string>
#include <cmath>
#include <vector>
#include <chrono>
#include <io.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgcodecs/imgcodecs.hpp>

#pragma comment (lib, "winmm.lib")

using namespace std;
namespace fs = filesystem;

// Prototypes
size_t number_of_files(fs::path path);

int main()
{
    // Defining the grey scale ramp
    string char_ramp("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\" ^ `'. ");
    reverse(char_ramp.begin(), char_ramp.end());

    
    // Ask user for input about the video
    string video_name("");
    int brightness(0);
    cout << "Mettez le terminal en plein ecran." << endl;
    cout << "Nom de la video et seuil de luminosite : ";
    cin >> video_name >> brightness;

    // Get terminal info
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
    const unsigned int target_frame_width(csbi.srWindow.Right - csbi.srWindow.Left + 1);
    const unsigned int target_frame_height(csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
    // Get video and audio path
    const string video_path("videos/" + video_name);
    const string audio_path("tmp/audio.wav");

    cout << "[1/3] Nettoyage..." << endl;
    // Delete previous folders and make new ones for the new video
    fs::remove_all(audio_path);
    fs::remove_all("tmp/frames");
    fs::create_directory("tmp/frames");

    cout << "[2/3] Extraction des images et de l'audio..." << endl;
    // Ffmpeg extracts frames and audio
    system(("ffmpeg -loglevel warning -i " + video_path + " -vf scale=" + to_string(target_frame_width) + ":" + to_string(target_frame_height) + " tmp/frames/%0d.bmp").c_str());
    system(("ffmpeg -loglevel warning -i " + video_path + " tmp/audio.wav").c_str());

    cout << "[3/3] Conversion..." << endl;
    const size_t number_of_frames(number_of_files("tmp/frames"));
    vector<string> frames(number_of_frames);
    // Frame to ASCII frame conversion
    for (int frame_index(1); frame_index <= number_of_frames; ++frame_index) {
        string frame_path("tmp/frames/" + to_string(frame_index) + ".bmp");
        string current_frame("");
        cv::Mat current_bmp(cv::imread(frame_path));
        for (int i(0); i < current_bmp.rows; ++i) {
            for (int j(0); j < current_bmp.cols; ++j) {
                // Le vecteur des couleurs B, G, R du pixel (i, j)
                cv::Vec3b bgr(current_bmp.at<cv::Vec3b>(i, j));
                // On utilise la luminance
                double greyscale_index(sqrt(0.299*bgr[2]*bgr[2] + 0.587*bgr[1]*bgr[1] + 0.114*bgr[0] *bgr[0]));
                current_frame += char_ramp[(int)(greyscale_index / 255 * (char_ramp.size() - 1)) / brightness];
            }
            current_frame += "\n";
        }
        frames[frame_index - 1] = current_frame;
    }
    // Grabbing audio duration
    FILE* fp;
    char var[40];
    double audio_duration(0);

    fp = _popen(("ffprobe -loglevel warning -i " + audio_path + " -show_entries format=duration -v quiet -of csv=\"p=0\"").c_str(), "r");
    while (fgets(var, sizeof(var), fp) != NULL) {
        audio_duration = atof(var);
    }
    _pclose(fp);

    PlaySound(TEXT("E:/Programmation/Cpp/SeuillageImage/tmp/audio.wav"), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);

    // Begin printing frames
    auto start_time(chrono::steady_clock::now());
    int current_index(0);
    int last_index(0);

    while (current_index < number_of_frames - 1) {
        // Computing the current index
        double elapsed_time(chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - start_time).count() / 1000.);
        double percentage(elapsed_time / audio_duration);
        current_index = percentage * number_of_frames;

        // Print the new frame if it's different from the last one to avoid spamming them
        if (current_index != last_index) {
            _write(_fileno(stdout), frames[current_index].c_str(), frames[current_index].size());
        }
        last_index = current_index;
    }

    // Stop audio
    PlaySound(NULL, NULL, 0);

    return 0;
}

size_t number_of_files(fs::path path) {
    return distance(fs::directory_iterator(path), fs::directory_iterator{});
}

这是该代码的 Python 版本。它使用 Python 成像库 (

PIL
) 和可选的
progress.bar
模块。它比 C++ 代码慢得多。

import argparse
import os
import shutil
import subprocess
import time
import sys
import wave
import winsound
from math import sqrt

from PIL import Image
from progress.bar import IncrementalBar

parser = argparse.ArgumentParser()
parser.add_argument("video_name", type=str, help="Nom de la vidéo")
parser.add_argument("brightness", type=int, help="Seuil de luminosité")
arguments = parser.parse_args()

# http://paulbourke.net/dataformats/asciiart/
# "Standard" character ramp for grey scale pictures from black to white
char_ramp = """$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,"^`'. """[::-1]

video_name = arguments.video_name
brightness = arguments.brightness
video_path = os.path.join(os.getcwd(), fr"videos\{video_name}")
audio_path = os.path.join(os.getcwd(), fr"tmp\audio.wav")

# ÉTAPE 1 : On supprime les fichiers déjà existants
print("[INFO] Étape 1 sur 3 : nettoyage...")
# On vérifie s'il existe déjà un dossier de stockage temporaire
if os.path.isdir("tmp"):
    # On supprime le dossier contenant les images de la vidéo et on en crée un nouveau
    if os.path.isdir("tmp/frames"):
        shutil.rmtree("tmp/frames", ignore_errors=False)
        os.mkdir("tmp/frames")
    # Idem pour le fichier audio correspondant
    if os.path.isfile("tmp/audio.wav"):
        os.remove("tmp/audio.wav")

# Sinon, on crée un nouveau dossier de stockage temporaire
else:
    os.mkdir("tmp")
    os.mkdir("tmp/frames")

# ÉTAPE 2 : On extrait les images successives de la vidéo avec ffmpeg une par une pour les convertir
print("[INFO] Étape 2 sur 3 : extraction des images et de l'audio...")
# On définit la longueur et la largeur de la fenêtre d'exécution
window_size = os.get_terminal_size()
target_frame_width = window_size[0] - 1
target_frame_height = window_size[1]

# On utilise le module externe ffmpeg pour extraire les images successives de la vidéo
subprocess.call(
    f"ffmpeg -loglevel warning -i {video_path} -vf scale={target_frame_width}:{target_frame_height} tmp/frames/%0d.bmp")
subprocess.call(f"ffmpeg -loglevel warning -i {video_path} tmp/audio.wav")

# ÉTAPE 3 : On convertit la vidéo en ASCII
print("[INFO] Étape 3 sur 3 : conversion de la vidéo en cours, cela peut prendre un certain temps...")
# On crée une liste dans laquelle on stocke les images et quelques autres variables utiles
frames = []
number_of_frames = len(
    [frame for frame in os.listdir("tmp/frames") if os.path.isfile(os.path.join("tmp/frames", frame))])
bar = IncrementalBar("Progression", max=number_of_frames)

for frame_index in range(1, number_of_frames + 1):
    frame_path = os.path.join(os.getcwd(), fr"tmp\frames\{frame_index}.bmp")
    # On construit l'image ASCII à partir de niveaux de gris dans l'image bitmap redimensionnée obtenue
    frame_builder = ""
    with Image.open(frame_path) as frame:
        # Pour chaque pixel de l'image
        for y in range(frame.height):
            for x in range(frame.width):
                # Conversion de chaque pixel en niveau de gris
                rgb = frame.getpixel((x, y))
                # On utilise la luminance plutôt que la moyenne RGB
                greyscale_index = sqrt(0.299 * rgb[0] ** 2 + 0.587 * rgb[1] ** 2 + 0.114 * rgb[2] ** 2)
                # On ajoute le caractère ASCII à la chaine une fois identifié (mapping linéaire)
                frame_builder += char_ramp[int(greyscale_index / 255 * (len(char_ramp) - 1)) // brightness]
            # On revient à la ligne pour chaque ligne traitée
            frame_builder += "\n"
    # On ajoute l'image Ascii crée à la liste des images de la vidéo
    frames.append(frame_builder)
    bar.next()
bar.finish()

# On initialise les fichiers audio et vidéo pour des calculs de fréquence
audio_wave = wave.open(audio_path, "rb")
audio_sample_rate = audio_wave.getframerate()
audio_frames = audio_wave.getnframes()
audio_duration = audio_frames / audio_sample_rate

# On joue la musique et on lance le chrono
winsound.PlaySound(audio_path, winsound.SND_ASYNC)
start_time = time.time()

# On imprime les frames en fonction de leur position dans le temps par rapport à l'audio (synchronisation)
current_frame = 0  # nombre de frames passées
last_frame = 0
while current_frame < len(frames) - 1:

    elapsed_time = time.time() - start_time
    percentage = (elapsed_time / float(audio_duration))
    current_frame = int(percentage * number_of_frames)

    if current_frame != last_frame:
        sys.stdout.write(frames[current_frame])
    last_frame = current_frame

我希望这些额外信息可以帮助您更好地理解我的问题。

c++ performance printing io
1个回答
0
投票

基本上,cmd.exe 对于我的程序来说太慢了。我切换到 Windows Terminal,它工作得很好。我还必须调整光标的位置,使其保持在 (0, 0) 以防止屏幕滚动。

© www.soinside.com 2019 - 2024. All rights reserved.