I. Introduction

Qui n'a jamais trouvé le téléchargement d'un fichier de plusieurs dizaines de mégas extrêmement long malgré une connexion Internet haut débit ? Vous ? Moi ?… Tout le monde !

Ce tutoriel va vous expliquer comment en accélérer le téléchargement à l'aide de plusieurs connexions simultanées en multi-threads et d'un fichier mappé.

Remarque :

Il ne s'agit pas de transformer un accès Internet à 15 Mb/s en un à 25 (faut pas rêver) mais simplement de passer outre la limitation de bande passante définie par l'hébergeur du site visé. Il faut par conséquent que le débit descendant de votre connexion soit supérieur à la limite en place. Dans le cas contraire, vous ne remarquerez aucune différence !

II. Introduction aux fichiers mappés

Ce tutoriel n'a pas pour but d'expliquer tous les détails du mappage de fichier. En voici seulement une courte introduction.

Un fichier mappé est l'association entre le contenu d'un fichier et une zone de la mémoire virtuelle d'un ou de plusieurs processus. C'est un pont entre le fichier et les processus.

Une vue est la portion de la zone de mémoire virtuelle qu'un processus utilise pour accéder au contenu du fichier. Elle peut représenter le fichier complet ou une partie seulement. En d'autres termes, une vue est un pointeur sur un fragment du fichier chargé dans la mémoire du processus.

III. Principe

Sans grande surprise, le but va être de lancer plusieurs connexions Internet simultanément et à travers chacune d'elles de télécharger un fragment du fichier.

Chaque connexion est réalisée dans un thread séparé (TViewDownloadThread). Ils sont créés au démarrage et synchronisés par Events.

Un thread principal (TDownloadThread) se charge de répartir le travail en fonction de l'occupation des threads de téléchargement.

Le téléchargement est réalisé à l'aide du composant INDY : TIdHttp.

IV. Nombre de tâches

Le nombre de tâches peut être supérieur au nombre de processeurs de la machine. On peut en effet partir du principe que le dialogue client/serveur à travers Internet est lent et que chaque thread passera un certain temps à attendre des réponses.

Il y a cependant certaines limitations qu'il serait inutile de dépasser :

  • une vue ne peut pas commencer à n'importe quel endroit d'un fichier mappé, elle doit respecter un alignement déterminé par le système. Cet alignement est récupéré à l'aide de l'API GetNativeSystemInfo. Il correspond à la « granularité d'allocation » et est normalement de 64 kB sous Windows.
    Dès lors, le nombre de threads ne devrait excéder : la taille du fichier à télécharger div 64 kB + 1. Si le fichier faisait 200 kB, le nombre de threads utilisés serait de quatre, même si on en créait dix.
    Dans notre exemple, la taille d'un bloc sera fixée à 15 fois l'allocation minimale. Un thread téléchargera donc environ 975 kB ce qui est un bon compromis entre l'alignement obligatoire des vues et le temps de latence. Il faudra cependant réduire ce multiplicateur si le débit de la connexion Internet est inférieur à 15 Mb/s.

    Voici comment récupérer la taille d'allocation minimale :
Taille d'allocation minimale
Sélectionnez
var
  SystemInfo :TSystemInfo;
  AllocSize  :Int64;
 
begin
  GetNativeSystemInfo(SystemInfo);
  AllocSize := SystemInfo.dwAllocationGranularity;
end;
  • la vitesse de la connexion Internet (downstream). 10 Mb/s permettraient dans le meilleur des cas une quinzaine de threads si 64 kB était utilisé ;
  • la qualité du service fourni par l'hébergeur du site. Limite de bande passante et du nombre de connexions concurrentes, temps de latence, etc. Certains serveurs n'acceptent tout simplement pas le téléchargement par fragment !

Créer plus de tâches que nécessaire a cependant un impact négligeable sur la fonction proposée ci-dessous. Les inutilisées sont placées en mode attente infinie par WaitForSingleObject(INFINITE) et ne consomment par conséquent aucun temps processeur.

V. Tâche de téléchargement

Les tâches de téléchargement sont en charge de récupérer un fragment du fichier. Elles pourraient être créées uniquement lorsqu'un nouveau bloc est requis. Ici, elles sont créées une fois pour toutes au démarrage et synchronisées par Events.

Déclaration
Sélectionnez
Type
  TViewDownloadThread = class(TThread)
  private
    URL         :string;
    MapFile     :THandle;
    View        :TViewInfo;
 
    Stream      :TMemoryStream;
    Http        :TIdHttp;
    RunEvent    :THandle;
    ReadyEvent  :Thandle;
  end;
  • URL est le fichier à télécharger.
  • MapFile est le fichier mappé auquel nous appliquerons une vue.
  • View est une structure contenant l'offset et la taille du bloc à télécharger.
  • Stream est le buffer de téléchargement.
  • Http est un TidHttp en charge de la connexion au serveur.
  • RunEvent est un Event local ordonnant le démarrage de la fonction en attente.
  • ReadyEvent est un Event fourni par le thread principal et signifie la fin du téléchargement du bloc et l'état prêt pour une nouvelle commande.

TViewInfo est déclaré ainsi :

TViewInfo
Sélectionnez
  TViewInfo = record
    Size :int64;
 
    case byte of
      0 : (Offset :int64);
      1 : (LoOffset, HiOffset :integer);
  end;

Size est la taille du bloc à télécharger. Le case permet de lire/écrire l'offset sous forme d'un int64 ou de deux entiers LowInteger et HighInteger sans devoir appliquer de transformation. MapViewOfFile que nous utiliserons bientôt requiert la deuxième forme.

V-A. Création et destruction

Les paramètres de création sont mémorisés et l'Event de démarrage ainsi que les objets nécessaires au téléchargement sont créés.

Création
Sélectionnez
constructor TViewDownloadThread.Create(aURL :string; aMapFile, aReadyEvent :THandle; aOnTerminate :TNotifyEvent);
begin
  inherited Create;
 
  URL         := aURL;
  MapFile     := aMapFile;
  ReadyEvent  := aReadyEvent;
  OnTerminate := aOnTerminate;
 
  RunEvent    := CreateEvent(nil, TRUE, FALSE, nil);
  Stream      := TMemoryStream.Create;
  Http        := TIdHTTP.Create(nil);
 
  HTTP.Request.Connection := 'keep-alive';
  Http.Request.UserAgent  := UserAgent;
 
  //Libération automatique
  FreeOnTerminate := TRUE;
end;

OnTerminate est défini sur une méthode de la tâche principale et permet de tester la fin du déroulement du thread par l'intermédiaire de la propriété ReturnValue.

Qui dit création, dit destruction. Voici le destructeur.

Destruction
Sélectionnez
destructor TViewDownloadThread.Destroy;
begin
  CloseHandle(RunEvent);
  Stream.Free;
  Http.Free;
 
  inherited;
end;

V-B. Start

Cette méthode est appelée depuis la tâche principale. Les offset et taille sont fixés, le thread est marqué comme occupé et enfin démarré.

Start
Sélectionnez
procedure TViewDownloadThread.Start(aOffset, aSize: int64);
begin
  View.Offset := aOffset;
  View.Size   := aSize;
 
  //Occupé et démarré
  ResetEvent(ReadyEvent);
  SetEvent(RunEvent);
end;

V-C. Stop

Le principe des threads de téléchargement est de ne pas générer d'activité s'il n'y a pas de bloc à récupérer. Ils sont mis dans un état d'attente infini. Pour les détruire, il est donc nécessaire de les réveiller pour leur permettre de sortir de la boucle de répétition.

Stop est appelé depuis la tâche principale une fois le téléchargement terminé.

Stop
Sélectionnez
procedure TViewDownloadThread.Stop;
begin
  Terminate;
  SetEvent(RunEvent);
end;

V-D. Execute

Voici comment se déroule le téléchargement d'un bloc une fois le démarrage signalé (RunEvent).

La sélection d'un fragment du fichier distant se fait en spécifiant dans l'entête de la requête les pointeurs de début et fin de bloc. Request.Range de TIdHttp est une chaîne de la forme début-fin.

Get récupère le bloc et remplit un stream. Si l'opération s'est bien déroulée, la vue sur le fichier mappé est créée en écriture et les données sont copiées en bit à bit du stream vers la vue.

Execute
Sélectionnez
procedure TViewDownloadThread.Execute;
var
  Data :pointer;
 
begin
  while not Terminated do
  begin
    //Attente infinie de l'ordre de démarrage
    WaitForSingleObject(RunEvent, INFINITE);
 
    if not Terminated then
    try
      ReturnValue := ERROR_FILE_NOT_FOUND;
 
      Stream.Clear;
 
      //Télécharge un bloc
      Http.Request.Range := Format('%d-%d',[View.Offset, View.Offset +View.Size -1]);
      Http.Get(URL, Stream);
 
      //Ouvre en écriture la vue correspondante du fichier local
      Data := MapViewOfFile(MapFile, FILE_MAP_WRITE, View.HiOffset, View.LoOffset, View.Size);
 
      if not Assigned(Data) then
      begin
        ReturnValue := GetLastError;
        Exit;
      end;
 
      //Copie les données et libère la vue
      CopyMemory(Data, Stream.Memory, View.Size);
      UnmapViewOfFile(Data);
 
      ReturnValue := ERROR_SUCCESS;
 
    finally
      //Arrêté et prêt
      ResetEvent(RunEvent);
      SetEvent(ReadyEvent);
    end;
  end;
end;

La vue est ensuite libérée et la tâche à nouveau marquée comme arrêtée et prête pour un nouvel ordre.

VI. Tâche principale

Le thread principal se charge de l'accès au fichier local, de la gestion des threads de téléchargement et de la notification d'avancement.

Voici la déclaration de ses propriétés :

Tâche principale
Sélectionnez
type
  TDownloadThread = class(TThread)
  private
    Wnd         :hWnd;
    Message     :Cardinal;
    URL         :string;
    FileName    :TFileName;
    FileSize    :int64;
    AllocSize   :int64;
    ThreadCount :integer;
    Error       :boolean;
end;
  • Wnd et Message vont nous permettre d'envoyer des informations sur l'état du téléchargement par PostMessage. Message peut correspondre à une constante WM_USER ou à un message enregistré par RegisterWindowMessage.
  • URL est bien entendu le fichier à télécharger et FileName le fichier local. L'URL doit commencer par « http:// ».
  • FileSize est la taille totale du fichier.
  • AllocSize est la taille d'un fragment de ce fichier.
  • ThreadCount est le nombre de threads à utiliser.
  • Error est un booléen passant à VRAI si une erreur est rencontrée dans la tâche principale ou dans celles de téléchargement.

En plus de ces propriétés, la variable TThread.ReturnValue est utilisée et contient l'état du téléchargement. ERROR_SUCCESS en cas de réussite, le code d'erreur d'une API ayant échoué ou ERROR_FILE_NOT_FOUND si le fichier distant n'a pu être localisé. ReturnValue est utilisé pour notifier l'erreur à l'utilisateur.

VI-A. Création

Rien de bien compliqué !

Les paramètres sont mémorisés, le nombre de tâches de téléchargement est fixé au nombre de CPU x 2 si 0 est passé à la fonction. La taille d'un bloc est définie en fonction de l'allocation minimale récupérée et le TThread est marqué comme autodétruit.

Création de la tâche principale
Sélectionnez
constructor TDownloadThread.Create(aWnd :hWnd; aMessage :Cardinal; aURL :string; aFileName :TFileName; aThreadCount :integer);
var
  SystemInfo :TSystemInfo;
 
begin
  inherited Create;
 
  Wnd      := aWnd;
  URL      := aURL;
  Message  := aMessage;
  FileName := aFileName;
  Error    := FALSE;
 
  //Fixe le nombre de threads à utiliser
  if aThreadCount > 0
  then ThreadCount := aThreadCount
  else ThreadCount := CPUCount *2;
 
  //Fixe la taille d'allocation minimale définie par le système
  GetNativeSystemInfo(SystemInfo);
  AllocSize := SystemInfo.dwAllocationGranularity *15;
 
  //Libération automatique
  FreeOnTerminate := TRUE;
end;

VI-B. Notification

Notification asynchrone par PostMessage de l'avancement du téléchargement. WParam est le type de notification. LParam dépend de ce type.

Notification
Sélectionnez
type
  TFastURLDownloadState = (dsStart, dsIncrement, dsError, dsSuccess);
 
procedure TDownloadThread.Notify(aState: TFastURLDownloadState; aLParam: LParam);
begin
  PostMessage(Wnd, Message, WParam(aState), aLParam);
end;
  • dsStart est envoyé en début du téléchargement mais après que la taille du fichier a été récupérée depuis le serveur. LParam contient la taille du fichier.
  • dsIncrement est envoyé au démarrage de chaque thread de téléchargement. LParam contient la position actuelle.
  • dsError est envoyé en cas d'erreur. LParam est le code d'erreur.
  • dsSuccess est envoyé lorsque le téléchargement s'est terminé avec succès. LParam contient la durée en millisecondes.

VI-C. OnTerminate

OnTerminate est l'événement généré à la fin du téléchargement d'un bloc par un thread de téléchargement et renseigne sur le succès de l'opération.

OnTerminate
Sélectionnez
procedure TDownloadThread.OnThreadTerminate(Sender: TObject);
begin
  with TThread(Sender) do
    if ReturnValue <> ERROR_SUCCESS then
    begin
      Error := TRUE;
      Notify(dsError, ReturnValue);
    end;
end;

VI-D. Informations sur le fichier et serveur distants

Pour connaître la taille du fichier distant et la capacité du serveur à nous en envoyer un fragment, il suffit de récupérer l'entête d'une réponse à l'aide de la méthode Head de TIdHttp et de consulter la variable Response :

  • ContentLength : contient la taille du fichier ;
  • AcceptRanges : contient « bytes » si le téléchargement par bloc est possible et « none » si non supporté.

Si le fichier n'est pas trouvé, l'erreur ERROR_FILE_NOT_FOUND est générée.

Taille du fichier distant
Sélectionnez
procedure TDownloadThread.GetFileInfo;
begin
  //Récupère la taille du fichier et la capacité du serveur
  //en ne téléchargeant que l'entête
  try
    with TIdHTTP.Create(nil) do
    try
      Request.UserAgent := UserAgent;
      Head(Self.URL);
 
      FileSize     := Response.ContentLength;
      AcceptRanges := Response.AcceptRanges = 'bytes';
    finally
      Free;
    end;
  except
    Notify(dsError, ERROR_FILE_NOT_FOUND);
    Raise;
  end;
end;

VI-E. Téléchargement simple

Si la taille du fichier est inférieure à la taille d'allocation d'une vue, si un seul thread est demandé ou tout simplement si le serveur ne supporte pas le téléchargement par bloc, inutile de nous fatiguer ! Un téléchargement simple est lancé à l'aide d'un TFileStream.

Téléchargement simple
Sélectionnez
procedure TDownloadThread.SingleDownload;
var
  Stream :TFileStream;
 
begin
  //Création du fichier local. "Accès refusé" si impossible
  try
    Stream := TFileStream.Create(FileName, fmCreate);
  except
    Notify(dsError, ERROR_ACCESS_DENIED);
    Raise;
  end;
 
  //Téléchargement
  try
    with TIdHTTP.Create(nil) do
    try
      Request.UserAgent := UserAgent;
      Get(Self.URL, Stream);
    finally
      Stream.Free;
      Free;
    end;
 
  except
    Notify(dsError, ERROR_FILE_NOT_FOUND);
    Raise;
  end;
end;

VI-F. Téléchargement multiple

Nous voici au cœur du système, la gestion du téléchargement multiconnexions.

Pour en expliquer clairement le contenu, cette procédure est fragmentée en plusieurs parties et pour plus de lisibilité le contrôle d'erreur a été supprimé. La procédure complète est visible en fin de chapitre.

Déclarations

Déclarations
Sélectionnez
procedure TDownloadThread.MultiDownload;
var
  FHandle    :hFile;
  hMap       :THandle;
  Offset     :int64;
  Size       :int64;
  Position   :int64;
  WaitState  :dword;
  i          :integer;
 
  ThreadList :array of TViewDownloadThread;
  ReadyList  :array of THandle;
  • FHandle est le handle du fichier local et hMap du fichier mappé.
  • Offset est le décalage de la vue par rapport au début du fichier mappé et Size sa taille.
  • Position est une simulation de l'avancement du téléchargement et correspond au nombre de blocs en téléchargement x la taille de leur vue.
  • WaitState est utilisé par la synchronisation et détermine qu'un thread est prêt à exécuter un nouveau travail.
  • ThreadList est la liste des tâches créées.
  • ReadyList est une liste d'Events indiquant l'état prêt de chaque tâche.

Création des fichiers

Le fichier local est créé par CreateFile en lecture et écriture et sa taille définitive est directement allouée. Le fichier mappé est ensuite créé sur le handle retourné.

Création des fichiers
Sélectionnez
FHandle := CreateFile(PChar(FileName), FILE_READ_DATA or FILE_WRITE_DATA, 0, nil, CREATE_ALWAYS, 0, 0);
 
SetFilePointerEx(FHandle, FileSize, nil, FILE_BEGIN);
SetEndOfFile(FHandle);
 
hMap := CreateFileMapping(FHandle, nil, PAGE_READWRITE, 0, 0, nil);

Il n'est en effet pas nécessaire d'écrire dans un fichier pour en augmenter sa taille. Assigner son pointeur interne (SetFilePointerEx) au-delà de la fin du fichier est parfaitement valide. Il est ensuite possible de fixer la fin de fichier sur ce pointeur (SetEndOfFile).

Création des Threads et Events

Les tâches de téléchargement sont créées ainsi qu'un nombre identique d'Events. Cette liste d'Events (ReadyList) permet de déterminer lorsqu'une tâche est terminée et prête à recevoir un nouvel ordre.

Création Threads et Events
Sélectionnez
SetLength(ReadyList, ThreadCount);
SetLength(ThreadList, ThreadCount);
 
for i := 0 to ThreadCount -1 do
begin
  ReadyList[i]  := CreateEvent(nil, TRUE, TRUE, nil);
  ThreadList[i] := TViewDownloadThread.Create(URL, hMap, ReadyList[i], OnThreadTerminate);
end;

Boucle de téléchargement

WaitForMultipleObjects est l'objet de synchronisation permettant de savoir qu'au moins un thread de la liste n'a pas de travail en cours. Il est tout de même réveillé toutes les secondes pour s'assurer que la tâche courante n'a pas été terminée (sortie du programme par exemple).

Lorsqu'un thread est prêt, il est redémarré (Start) sur une vue correspondant à Offset et Size.

La boucle est terminée en cas d'erreur ou lorsque Size est plus petit qu'AllocSize signifiant que toutes les données ont été téléchargées ou sont en passe de l'être.

Boucle de téléchargement
Sélectionnez
while not (Terminated or Error) do
begin
  WaitState := WaitForMultipleObjects(ThreadCount, @ReadyList[0], FALSE, 1000);
 
  if WaitState <> WAIT_TIMEOUT then
  begin
    i    := WaitState -WAIT_OBJECT_0;
    Size := Min(FileSize -Offset, AllocSize);
 
    if Size > 0 then
    begin
      ThreadList[i].Start(Offset, Size);
      inc(Position, Size);
      Notify(dsIncrement, Position);
    end;
 
    if Size < AllocSize
    then Break
    else inc(Offset, AllocSize);
  end;
end;

Attente de fin

Spécifier VRAI pour le paramètre bWaitAll ordonne à la fonction WaitForMultipleObjects d'attendre que tous les Events soient signalés. Si nous ne le faisions pas, le thread principal serait détruit avant que le téléchargement soit effectivement terminé.

Attente de fin
Sélectionnez
WaitForMultipleObjects(ThreadCount, @ReadyList[0], TRUE, INFINITE);

Les libérations

Le téléchargement est terminé ou une erreur est survenue, il est temps de procéder à quelques nettoyages. Destruction des threads et libération des Events, fermeture des fichiers et effacement du fichier local en cas d'erreur.

Libération
Sélectionnez
for i := 0 to ThreadCount -1 do
begin
  ThreadList[i].Stop;
  CloseHandle(ReadyList[i]);
end;
 
CloseHandle(hMap);
CloseHandle(FHandle);
 
if Error then
  DeleteFile(FileName);

VI-F-1. Procédure complète

Voici la procédure complète avec gestion des erreurs et commentaires.

Procédure complète
Cacher/Afficher le codeSélectionnez

VI-G. Execute

Il ne nous reste plus qu'à implémenter la méthode Execute du thread.

C'est ici que nous allons déterminer la taille du fichier à télécharger, le nombre de threads réellement utilisés et par conséquent choisir entre le mode de téléchargement simple ou multiple.

Boucle Execute
Sélectionnez
procedure TDownloadThread.Execute;
var
  StartTime :Cardinal;
 
begin
  //Informations sur le fichier et la capacité du serveur
  GetFileInfo;
 
  //Contrôle le nombre de Threads à utiliser
  if ThreadCount > 1 then
  begin
    if AcceptRanges and (FileSize > AllocSize)
    then ThreadCount := Min(ThreadCount, FileSize div AllocSize)
    else ThreadCount := 1;
  end;
 
  Notify(dsStart, FileSize);
 
  StartTime := GetTickCount;
 
  //Téléchargement
  if ThreadCount > 1
  then MultiDownload
  else SingleDownload;
 
  //Si pas eu d'erreur avant
  Notify(dsSuccess, GetTickCount -StartTime);
end;

VII. Procédure FastURLDownload

FastURLDownload est la procédure accessible depuis l'extérieur de l'unité utilisée pour lancer le téléchargement.

FastURLDownload
Sélectionnez
procedure FastURLDownload(aWnd :hWnd; aMessage :Cardinal; aURL :string; aFileName :TFileName; aThreadCount :integer = 0);
  • aWnd est la fenêtre devant recevoir les notifications d'avancement.
  • aMessage est le message à envoyer. Cela peut être une constante WM_USER ou un message enregistré par RegisterWindowMessage.
  • aURL est le fichier à télécharger. aURL doit commencer par « http:// »
  • aFileName est le fichier local.
  • aThreadCount est le nombre de tâches de téléchargement simultanées désirées. Si 0, il correspond au nombre de processeurs disponibles sur la machine x 2.

VIII. Utilisation

Et voici comment appeler cette procédure dans votre application.

Appel
Sélectionnez
procedure TForm1.Button1Click(Sender: TObject);
begin
  FastURLDownload(Handle, WM_USER, 'http://www.domaine.com/fichier.exe', 'c:\RépertoireLocal\fichier.exe', CPUCount *3);
end;

Et pour recevoir les notifications, une fiche contenant un mémo et une barre de progression.

Notifications
Sélectionnez
type
  TForm1 = class(TForm)
    Memo1: TMemo;
    ProgressBar1: TprogressBar;
  protected
    procedure WMFastDownloadNotify(var Message :TMessage); message WM_USER;
  end;
 
procedure TForm1.WMFastDownloadNotify(var Message: TMessage);
begin
  with Message do
    case TFastURLDownloadState(WParam) of
      dsStart     : //Démarrage
                    begin
                      Memo1.Lines.Add('Téléchargement commencé à ' +DateTimeToStr(Now));
                      Memo1.Lines.Add(Format('Taille du fichier : %d octets', [LParam]));
                      ProgressBar1.Max := LParam;
                      ProgressBar1.Position := 0;
                    end;
 
      dsIncrement : //Incrémentation
                    ProgressBar1.Position := LParam;
 
      dsError     : //Erreur
                    Memo1.Lines.Add(Format('Erreur %d : %s', [LParam, SysErrorMessage(LParam)]));
 
      dsSuccess   : //Terminé avec succès
                    Memo1.Lines.Add(Format('Téléchargement terminé en %fs', [LParam /1000]));
    end;
end;

Simple non ?

IX. Mesures

Voici quelques mesures réalisées à partir de deux sites différents. Le fichier est identique et correspond à la version Delphi 6 Perso. Sa taille est de 146 646 147 octets.

Le premier site est bien sûr Developpez.net. Le deuxième est hébergé chez Infomaniak.

Ces mesures sont faites sur une modeste machine bicœur tournant à 2 GHz et le débit descendant de la connexion Internet est de 50 Mb/s.

Les tableaux indiquent :

  • le nombre de tâches utilisées, soit le nombre de connexions concurrentes ;
  • la charge moyenne du système (en pour cent) ;
  • le débit Internet maximum (en Mb/s) ;
  • le temps total (en secondes).

Une tâche équivaut au téléchargement « simple ». Deux et plus au téléchargement « multiple ».

Accrochez-vous, ça va décoiffer !

Infomaniak :

Nombre de tâches Charge CPU (%) Débit Internet (Mb/s) Temps (s)
1 20 26 51
2 45 34 38
4 80 52 24

Quatre tâches suffisent pour atteindre le débit maximum de 50 Mb/s.

Voici les graphiques de l'activité des processeurs et du débit :

Deux tâches Quatre tâches (débit max)
Image non disponible Image non disponible
Image non disponible Image non disponible

Developpez.net :

Nombre de tâches Charge CPU (%) Débit Internet (Mb/s) Temps (s)
1 12 11 172
2 25 20 70
4 60 36 36
10 95 48 32
12 95 52 24

La bande passante est plus limitée sur le site de DVP. Douze tâches sont nécessaires pour atteindre le débit maximum et l'activité des cœurs est proche de la saturation.

Les graphiques correspondants :

Deux tâches Douze tâches (débit max)
Image non disponible Image non disponible
Image non disponible Image non disponible

Ces mesures permettent de constater que la limitation se situe principalement au niveau du débit descendant mais confirment également, si on en doutait encore, que la charge CPU dépend fortement du nombre de tâches en cours.

Elles permettent aussi accessoirement de constater les différences entre hébergeurs ; la qualité de leurs services. Alors qu'il ne faut que quatre tâches chez Infomaniak pour atteindre le débit maximum, douze sont nécessaires chez l'hébergeur DVP. À certaines heures, il a même fallu monter à vingt-six (je le rappelle, sur un PC bicœur) ! Le temps de téléchargement par un seul thread était alors de 499 secondes, mais… il était toujours possible de télécharger à 50 Mb/s !

Nous remarquons également que les connexions multiples ne sont d'aucune utilité chez Infomaniak si vous ne disposez que d'un accès Internet à 10 Mb/s puisque la limite de débit est supérieure à 12.

À noter encore qu'un test chez free.fr s'est révélé catastrophique ! L'impossibilité d'avoir plus de trois tâches concurrentes et un temps de latence exécrable rendent le téléchargement monothread plus rapide !

X. Conclusion

Nous arrivons au bout de ce tutoriel.

Le principe évoqué ici pourrait bien sûr encore être amélioré avec création des threads en fonction du débit mesuré et inclure des fonctions Suspend/Resume avec sauvegarde de l'avancement pour une reprise après redémarrage ou une plus grande tolérance aux erreurs. Un véritable Download Manager !

La source de l'unité FastURLDownload.pas est téléchargeable au format zip.

FastURLDownloads.zip

Sur ce et en espérant vous avoir intéressé, Andnotor vous souhaite le bonjour ;-)

XI. Remerciements

Un grand merci à ClaudeLELOUP pour la relecture orthographique.

XII. Références