Download ebooks file Using OpenCL Programming Massively Parallel Computers J. Kowalik all chapters
Download ebooks file Using OpenCL Programming Massively Parallel Computers J. Kowalik all chapters
com
https://ebookname.com/product/using-opencl-programming-
massively-parallel-computers-j-kowalik/
OR CLICK BUTTON
DOWLOAD EBOOK
https://ebookname.com/product/computers-and-programming-1st-edition-
lisa-mccoy/
ebookname.com
https://ebookname.com/product/hidden-structure-music-analysis-using-
computers-david-cope/
ebookname.com
https://ebookname.com/product/using-computers-in-linguistics-a-
practical-guide-1st-edition-john-m-lawler/
ebookname.com
The Dragon and the Eagle The Rise and Fall of the Chinese
and Roman Empires 1st edition (April 4, 2014) Edition
Sunny Y. Auyang
https://ebookname.com/product/the-dragon-and-the-eagle-the-rise-and-
fall-of-the-chinese-and-roman-empires-1st-edition-
april-4-2014-edition-sunny-y-auyang/
ebookname.com
Family Therapy Techniques Integrating and Tailoring
Treatment 1st Edition Jon Carlson
https://ebookname.com/product/family-therapy-techniques-integrating-
and-tailoring-treatment-1st-edition-jon-carlson/
ebookname.com
https://ebookname.com/product/the-physics-and-technology-of-ion-
sources-2ed-edition-brown-i-g-ed/
ebookname.com
https://ebookname.com/product/attosecond-and-xuv-spectroscopy-
ultrafast-dynamics-and-spectroscopy-1st-edition-thomas-schultz/
ebookname.com
https://ebookname.com/product/topsy-and-tim-go-to-the-zoo-jean-
adamson/
ebookname.com
Elephant Sense and Sensibility 1st Edition Michael
Garstang
https://ebookname.com/product/elephant-sense-and-sensibility-1st-
edition-michael-garstang/
ebookname.com
USING OPENCL
Advances in Parallel Computing
This book series publishes research and development results on all aspects of parallel
computing. Topics may include one or more of the following: high-speed computing
architectures (Grids, clusters, Service Oriented Architectures, etc.), network technology,
performance measurement, system software, middleware, algorithm design,
development tools, software engineering, services and applications.
Series Editor:
Professor Dr. Gerhard R. Joubert
Volume 21
Recently published in this series
Vol. 20. I. Foster, W. Gentzsch, L. Grandinetti and G.R. Joubert (Eds.), High
Performance Computing: From Grids and Clouds to Exascale
Vol. 19. B. Chapman, F. Desprez, G.R. Joubert, A. Lichnewsky, F. Peters and T. Priol
(Eds.), Parallel Computing: From Multicores and GPU’s to Petascale
Vol. 18. W. Gentzsch, L. Grandinetti and G. Joubert (Eds.), High Speed and Large Scale
Scientific Computing
Vol. 17. F. Xhafa (Ed.), Parallel Programming, Models and Applications in Grid and
P2P Systems
Vol. 16. L. Grandinetti (Ed.), High Performance Computing and Grids in Action
Vol. 15. C. Bischof, M. Bücker, P. Gibbon, G.R. Joubert, T. Lippert, B. Mohr and F.
Peters (Eds.), Parallel Computing: Architectures, Algorithms and Applications
Janu
usz Kow
walik
1647
77-107th PL NE, Bothell,, WA 98011
1, USA
and
Tadeusz PuĨnia
akowski
UG, MFI, Wit
W Stwosz Street
S 57, 80-952
8 GdaĔĔsk, Poland
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or
transmitted, in any form or by any means, without prior written permission from the publisher.
Publisher
IOS Press BV
Nieuwe Hemweg 6B
1013 BG Amsterdam
Netherlands
fax: +31 20 687 0019
e-mail: order@iospress.nl
LEGAL NOTICE
The publisher is not responsible for the use which might be made of the following information.
v
vi
Preface
This book contains the most important and essential information required for de-
signing correct and efficient OpenCL programs. Some details have been omitted but
can be found in the provided references. The authors assume that readers are famil-
iar with basic concepts of parallel computation, have some programming experience
with C or C++ and have a fundamental understanding of computer architecture.
In the book, all terms, definitions and function signatures have been copied from
official API documents available on the page of the OpenCL standards creators.
The book was written in 2011, when OpenCL was in transition from its infancy
to maturity as a practical programming tool for solving real-life problems in science
and engineering. Earlier, the Khronos Group successfully defined OpenCL specifica-
tions, and several companies developed stable OpenCL implementations ready for
learning and testing. A significant contribution to programming heterogeneous com-
puters was made by NVIDIA which created one of the first working systems for pro-
gramming massively parallel computers – CUDA. OpenCL has borrowed from CUDA
several key concepts. At this time (fall 2011), one can install OpenCL on a hetero-
geneous computer and perform meaningful computing experiments. Since OpenCL
is relatively new, there are not many experienced users or sources of practical infor-
mation. One can find on the Web some helpful publications about OpenCL, but there
is still a shortage of complete descriptions of the system suitable for students and
potential users from the scientific and engineering application communities.
Chapter 1 provides short but realistic examples of codes using MPI and OpenMP
in order for readers to compare these two mature and very successful systems with
the fledgling OpenCL. MPI used for programming clusters and OpenMP for shared
memory computers, have achieved remarkable worldwide success for several rea-
sons. Both have been designed by groups of parallel computing specialists that per-
fectly understood scientific and engineering applications and software development
tools. Both MPI and OpenMP are very compact and easy to learn. Our experience
indicates that it is possible to teach scientists or students whose disciplines are other
than computer science how to use MPI and OpenMP in a several hours time. We
hope that OpenCL will benefit from this experience and achieve, in the near future,
a similar success.
Paraphrasing the wisdom of Albert Einstein, we need to simplify OpenCL as
much as possible but not more. The reader should keep in mind that OpenCL will
be evolving and that pioneer users always have to pay an additional price in terms
of initially longer program development time and suboptimal performance before
they gain experience. The goal of achieving simplicity for OpenCL programming re-
quires an additional comment. OpenCL supporting heterogeneous computing offers
us opportunities to select diverse parallel processing devices manufactured by differ-
ent vendors in order to achieve near-optimal or optimal performance. We can select
multi-core CPUs, GPUs, FPGAs and other parallel processing devices to fit the prob-
lem we want to solve. This flexibility is welcomed by many users of HPC technology,
but it has a price.
Programming heterogeneous computers is somewhat more complicated than
writing programs in conventional MPI and OpenMP. We hope this gap will disappear
as OpenCL matures and is universally used for solving large scientific and engineer-
ing problems.
vii
Acknowledgements
It is our pleasure to acknowledge assistance and contributions made by several per-
sons who helped us in writing and publishing the book.
First of all, we express our deep gratitude to Prof. Gerhard Joubert who has
accepted the book as a volume in the book series he is editing, Advances in Parallel
Computing. We are proud to have our book in his very prestigious book series.
Two members of the Khronos organization, Elizabeth Riegel and Neil Trevett,
helped us with evaluating the initial draft of Chapter 2 Fundamentals and provided
valuable feedback. We thank them for the feedback and for their offer of promoting
the book among the Khronos Group member companies.
Our thanks are due to NVIDIA for two hardware grants that enabled our com-
puting work related to the book.
Our thanks are due to Piotr Arłukowicz, who contributed two sections to the
book and helped us with editorial issues related to using LATEX and the Blender3D
modeling open-source program.
We thank two persons who helped us improve the book structure and the lan-
guage. They are Dominic Eschweiler from FIAS, Germany and Roberta Scholz from
Redmond, USA.
We also thank several friends and family members who helped us indirectly by
supporting in various ways our book writing effort.
Janusz Kowalik
Tadeusz Puźniakowski
viii
Contents
1 Introduction 1
1.1 Existing Standard Parallel Programming Systems . . . . . . . . . . . . . . 1
1.1.1 MPI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.2 OpenMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2 Two Parallelization Strategies: Data Parallelism and Task Parallelism . 9
1.2.1 Data Parallelism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2.2 Task Parallelism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2.3 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3 History and Goals of OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3.1 Origins of Using GPU in General Purpose Computing . . . . . . 12
1.3.2 Short History of OpenCL . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4 Heterogeneous Computer Memories and Data Transfer . . . . . . . . . . 14
1.4.1 Heterogeneous Computer Memories . . . . . . . . . . . . . . . . . 14
1.4.2 Data Transfer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.4.3 The Fourth Generation CUDA . . . . . . . . . . . . . . . . . . . . . 15
1.5 Host Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.5.1 Phase a. Initialization and Creating Context . . . . . . . . . . . . 17
1.5.2 Phase b. Kernel Creation, Compilation and Preparations . . . . . 17
1.5.3 Phase c. Creating Command Queues and Kernel Execution . . . 17
1.5.4 Finalization and Releasing Resource . . . . . . . . . . . . . . . . . 18
1.6 Applications of Heterogeneous Computing . . . . . . . . . . . . . . . . . . 18
1.6.1 Accelerating Scientific/Engineering Applications . . . . . . . . . 19
1.6.2 Conjugate Gradient Method . . . . . . . . . . . . . . . . . . . . . . 19
1.6.3 Jacobi Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.6.4 Power Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.6.5 Monte Carlo Methods . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.6.6 Conclusions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
1.7 Benchmarking CGM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
1.7.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
1.7.2 Additional CGM Description . . . . . . . . . . . . . . . . . . . . . . 24
1.7.3 Heterogeneous Machine . . . . . . . . . . . . . . . . . . . . . . . . 24
1.7.4 Algorithm Implementation and Timing Results . . . . . . . . . . 24
1.7.5 Conclusions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
ix
2 OpenCL Fundamentals 27
2.1 OpenCL Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.1.1 What is OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.1.2 CPU + Accelerators . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.1.3 Massive Parallelism Idea . . . . . . . . . . . . . . . . . . . . . . . . 27
2.1.4 Work Items and Workgroups . . . . . . . . . . . . . . . . . . . . . . 29
2.1.5 OpenCL Execution Model . . . . . . . . . . . . . . . . . . . . . . . . 29
2.1.6 OpenCL Memory Structure . . . . . . . . . . . . . . . . . . . . . . . 30
2.1.7 OpenCL C Language for Programming Kernels . . . . . . . . . . . 30
2.1.8 Queues, Events and Context . . . . . . . . . . . . . . . . . . . . . . 30
2.1.9 Host Program and Kernel . . . . . . . . . . . . . . . . . . . . . . . . 31
2.1.10 Data Parallelism in OpenCL . . . . . . . . . . . . . . . . . . . . . . 31
2.1.11 Task Parallelism in OpenCL . . . . . . . . . . . . . . . . . . . . . . 32
2.2 How to Start Using OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.2.1 Header Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.2.2 Libraries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.2.3 Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.3 Platforms and Devices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.3.1 OpenCL Platform Properties . . . . . . . . . . . . . . . . . . . . . . 36
2.3.2 Devices Provided by Platform . . . . . . . . . . . . . . . . . . . . . 37
2.4 OpenCL Platforms – C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
2.5 OpenCL Context to Manage Devices . . . . . . . . . . . . . . . . . . . . . . 41
2.5.1 Different Types of Devices . . . . . . . . . . . . . . . . . . . . . . . 43
2.5.2 CPU Device Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
2.5.3 GPU Device Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
2.5.4 Accelerator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
2.5.5 Different Device Types – Summary . . . . . . . . . . . . . . . . . . 44
2.5.6 Context Initialization – by Device Type . . . . . . . . . . . . . . . 45
2.5.7 Context Initialization – Selecting Particular Device . . . . . . . . 46
2.5.8 Getting Information about Context . . . . . . . . . . . . . . . . . . 47
2.6 OpenCL Context to Manage Devices – C++ . . . . . . . . . . . . . . . . . 48
2.7 Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.7.1 Checking Error Codes . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.7.2 Using Exceptions – Available in C++ . . . . . . . . . . . . . . . . 53
2.7.3 Using Custom Error Messages . . . . . . . . . . . . . . . . . . . . . 54
2.8 Command Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
2.8.1 In-order Command Queue . . . . . . . . . . . . . . . . . . . . . . . 55
2.8.2 Out-of-order Command Queue . . . . . . . . . . . . . . . . . . . . 57
2.8.3 Command Queue Control . . . . . . . . . . . . . . . . . . . . . . . 60
2.8.4 Profiling Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
2.8.5 Profiling Using Events – C example . . . . . . . . . . . . . . . . . . 61
2.8.6 Profiling Using Events – C++ example . . . . . . . . . . . . . . . 63
2.9 Work-Items and Work-Groups . . . . . . . . . . . . . . . . . . . . . . . . . 65
2.9.1 Information About Index Space from a Kernel . . . . . . . . . . 66
2.9.2 NDRange Kernel Execution . . . . . . . . . . . . . . . . . . . . . . 67
2.9.3 Task Execution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
2.9.4 Using Work Offset . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
x
2.10 OpenCL Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
2.10.1 Different Memory Regions – the Kernel Perspective . . . . . . . . 71
2.10.2 Relaxed Memory Consistency . . . . . . . . . . . . . . . . . . . . . 73
2.10.3 Global and Constant Memory Allocation – Host Code . . . . . . 75
2.10.4 Memory Transfers – the Host Code . . . . . . . . . . . . . . . . . . 78
2.11 Programming and Calling Kernel . . . . . . . . . . . . . . . . . . . . . . . . 79
2.11.1 Loading and Compilation of an OpenCL Program . . . . . . . . . 81
2.11.2 Kernel Invocation and Arguments . . . . . . . . . . . . . . . . . . 88
2.11.3 Kernel Declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
2.11.4 Supported Scalar Data Types . . . . . . . . . . . . . . . . . . . . . 90
2.11.5 Vector Data Types and Common Functions . . . . . . . . . . . . . 92
2.11.6 Synchronization Functions . . . . . . . . . . . . . . . . . . . . . . . 94
2.11.7 Counting Parallel Sum . . . . . . . . . . . . . . . . . . . . . . . . . 96
2.11.8 Parallel Sum – Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . 97
2.11.9 Parallel Sum – Host Program . . . . . . . . . . . . . . . . . . . . . 100
2.12 Structure of the OpenCL Host Program . . . . . . . . . . . . . . . . . . . . 103
2.12.1 Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
2.12.2 Preparation of OpenCL Programs . . . . . . . . . . . . . . . . . . . 106
2.12.3 Using Binary OpenCL Programs . . . . . . . . . . . . . . . . . . . . 107
2.12.4 Computation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
2.12.5 Release of Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
2.13 Structure of OpenCL host Programs in C++ . . . . . . . . . . . . . . . . . 114
2.13.1 Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
2.13.2 Preparation of OpenCL Programs . . . . . . . . . . . . . . . . . . . 115
2.13.3 Using Binary OpenCL Programs . . . . . . . . . . . . . . . . . . . . 116
2.13.4 Computation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
2.13.5 Release of Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
2.14 The SAXPY Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
2.14.1 Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
2.14.2 The Example SAXPY Application – C Language . . . . . . . . . . 123
2.14.3 The example SAXPY application – C++ language . . . . . . . . 128
2.15 Step by Step Conversion of an Ordinary C Program to OpenCL . . . . . 131
2.15.1 Sequential Version . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
2.15.2 OpenCL Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . 132
2.15.3 Data Allocation on the Device . . . . . . . . . . . . . . . . . . . . . 134
2.15.4 Sequential Function to OpenCL Kernel . . . . . . . . . . . . . . . 135
2.15.5 Loading and Executing a Kernel . . . . . . . . . . . . . . . . . . . . 136
2.15.6 Gathering Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
2.16 Matrix by Vector Multiplication Example . . . . . . . . . . . . . . . . . . . 139
2.16.1 The Program Calculating mat r i x × vec t or . . . . . . . . . . . . 140
2.16.2 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
2.16.3 Experiment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
2.16.4 Conclusions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
xi
3.1.2 Detecting Available Extensions from API . . . . . . . . . . . . . . 148
3.1.3 Using Runtime Extension Functions . . . . . . . . . . . . . . . . . 149
3.1.4 Using Extensions from OpenCL Program . . . . . . . . . . . . . . 153
3.2 Debugging OpenCL codes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
3.2.1 Printf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
3.2.2 Using GDB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
3.3 Performance and Double Precision . . . . . . . . . . . . . . . . . . . . . . 162
3.3.1 Floating Point Arithmetics . . . . . . . . . . . . . . . . . . . . . . . 162
3.3.2 Arithmetics Precision – Practical Approach . . . . . . . . . . . . . 165
3.3.3 Profiling OpenCL Application . . . . . . . . . . . . . . . . . . . . . 172
3.3.4 Using the Internal Profiler . . . . . . . . . . . . . . . . . . . . . . . 173
3.3.5 Using External Profiler . . . . . . . . . . . . . . . . . . . . . . . . . 180
3.3.6 Effective Use of Memories – Memory Access Patterns . . . . . . . 183
3.3.7 Matrix Multiplication – Optimization Issues . . . . . . . . . . . . 189
3.4 OpenCL and OpenGL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
3.4.1 Extensions Used . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
3.4.2 Libraries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
3.4.3 Header Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
3.4.4 Common Actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
3.4.5 OpenGL Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . 198
3.4.6 OpenCL Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . 201
3.4.7 Creating Buffer for OpenGL and OpenCL . . . . . . . . . . . . . . 203
3.4.8 Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
3.4.9 Generating Effect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
3.4.10 Running Kernel that Operates on Shared Buffer . . . . . . . . . . 215
3.4.11 Results Display . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
3.4.12 Message Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
3.4.13 Cleanup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
3.4.14 Notes and Further Reading . . . . . . . . . . . . . . . . . . . . . . . 221
3.5 Case Study – Genetic Algorithm . . . . . . . . . . . . . . . . . . . . . . . . 221
3.5.1 Historical Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
3.5.2 Terminology . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
3.5.3 Genetic Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
3.5.4 Example Problem Definition . . . . . . . . . . . . . . . . . . . . . . 225
3.5.5 Genetic Algorithm Implementation Overview . . . . . . . . . . . 225
3.5.6 OpenCL Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
3.5.7 Most Important Elements of Host Code . . . . . . . . . . . . . . . 234
3.5.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
3.5.9 Experiment Results . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
xii
A.2.2 Blocks and Threads Indexing Formulas . . . . . . . . . . . . . . . 257
A.2.3 Runtime Error Handling . . . . . . . . . . . . . . . . . . . . . . . . 260
A.2.4 CUDA Driver API Example . . . . . . . . . . . . . . . . . . . . . . . 262
xiii
xiv
Chapter 1
Introduction
1.1.1. MPI
MPI is a programming system but not a programming language. It is a library of func-
tions for C and subroutines for FORTRAN that are used for message communication
between parallel processes created by MPI. The message-passing computing model
(Fig. 1.1) is a collection of interconnected processes that use their local memories
exclusively. Each process has an individual address identification number called the
rank. Ranks are used for sending and receiving messages and for workload distribu-
tion. There are two kinds of messages: point-to-point messages and collective mes-
sages. Point-to-point message C functions contain several parameters: the address of
the sender, the address of the receiver, the message size and type and some additional
information. In general, message parameters describe the nature of the transmitted
data and the delivery envelope description.
The collection of processes that can exchange messages is called the communica-
tor. In the simplest case there is only one communicator and each process is assigned
to one processor. In more general settings, there are several communicators and sin-
gle processors serve several processes. A processor rank is usually an integer number
running from 0 to p-1 where p is the total number of processes. It is also possible to
number processes in a more general way than by consecutive integer numbers. For
example, their address ID can be a double number such as a point (x, y) in Carte-
1
sian space. This method of identifying processes may be very helpful in handling ma-
trix operations or partial differential equations (PDEs) in two-dimensional Cartesian
space.
The C loop below will be executed by all processes from the process from
my_rank equal to 0 to my_rank equal to p − 1.
1 {
2 int i;
3 for (i = my_rank*N; i < (my_rank+1)*N; i++)
4 z[i]=a*x[i]+y[i];
5 }
2
For example, the process with my_rank=1 will add one thousand ele-
ments a*x[i] and y[i] from i=N =1000 to 2N –1=1999. The process with the
my_rank=p–1=9 will add elements from i=n–N =9000 to n–1=9999.
One can now appreciate the usefulness of assigning a rank ID to each process.
This simple concept makes it possible to send and receive messages and share the
workloads as illustrated above. Of course, before each process can execute its code
for computing a part of the vector z it must receive the needed data. This can be
done by one process initializing the vectors x and y and sending appropriate groups
of data to all remaining processes. The computation of SAXPY is an example of data
parallelism. Each process executes the same code on different data.
To implement function parallelism where several processes execute different pro-
grams process ranks can be used. In the SAXPY example the block of code containing
the loop will be executed by all p processes without exception. But if one process, for
example, the process rank 0, has to do something different than all others this could
be accomplished by specifying the task as follows:
1 if (my_rank == 0)
2 {execute specified here task}
This block of code will be executed only by the process with my_rank==0. In the
absence of "if (my_rank==something)" instructions, all processes will execute the
block of code {execute this task}. This technique can be used for a case where
processes have to perform several different computations required in the task-parallel
algorithm. MPI is universal. It can express every kind of algorithmic parallelism.
An important concern in parallel computing is the efficiency issue. MPI often
can be computationally efficient because all processes use only local memories. On
the other hand, processes require network communication to place data in proper
process memories in proper times. This need for moving data is called the algorithmic
synchronization, and it creates the communication overhead, impacting negatively
the parallel program performance. It might significantly damage performance if the
program sends many short messages. The communication overhead can be reduced
by grouping data for communication. By grouping data for communication created
are larger user defined data types for larger messages to be sent less frequently. The
benefit is avoiding the latency times.
On the negative side of the MPI evaluation score card is its inability for incre-
mental (part by part) conversion from a serial code to an MPI parallel code that
is algorithmically equivalent. This problem is attributed to the relatively high level
of MPI program design. To design an MPI program, one has to modifiy algorithms.
This work is done at an earlier stage of the parallel computing process than develop-
ing parallel codes. Algorithmic level modification usually can’t be done piecewise. It
has to be done all at once. In contrast, in OpenMP a programmer makes changes to
sequential codes written in C or FORTRAN.
Fig. 1.2 shows the difference. The code level modification makes possible incre-
mental conversions. In a commonly practiced conversion technique, a serial code is
converted incrementally from the most compute-intensive program parts to the least
compute intensive parts until the parallelized code runs sufficiently fast. For further
study of MPI, the book [1] is highly recommended.
3
1. Mathematical model.
2. Computational model.
3. Numerical algorithm and parallel conversion: MPI
4. Serial code and parallel modification: OpenMP
5. Computer runs.
Figure 1.2: The computer solution process. OpenMP will be discussed in the next
section.
1.1.2. OpenMP
OpenMP is a shared address space computer application programming interface
(API). It contains a number of compiler directives that instruct C/C++ or FORTRAN
“OpenMP aware” compilers to execute certain instructions in parallel and distribute
the workload among multiple parallel threads. Shared address space computers fall
into two major groups: centralized memory multiprocessors, also called Symmetric
Multi-Processors (SMP) as shown in Fig. 1.3, and Distributed Shared Memory (DSM)
multiprocessors whose common representative is the cache coherent Non Uniform
Memory Access architecture (ccNUMA) shown in Fig. 1.4.
SMP computers are also called Uniform Memory Access (UMA). They were the
first commercially successful shared memory computers and they still remain popu-
lar. Their main advantage is uniform memory access. Unfortunately for large num-
bers of processors, the bus becomes overloaded and performance deteriorates. For
this reason, the bus-based SMP architectures are limited to about 32 or 64 proces-
sors. Beyond these sizes, single address memory has to be physically distributed.
Every processor has a chunk of the single address space.
Both architectures have single address space and can be programmed by using
OpenMP. OpenMP parallel programs are executed by multiple independent threads
4
Figure 1.4: ccNUMA with cache coherent interconnect.
that are streams of instructions having access to shared and private data, as shown
in Fig. 1.5.
The programmer can explicitly define data that are shared and data that are
private. Private data can be accessed only by the thread owning these data. The flow
of OpenMP computation is shown in Fig. 1.6. One thread, the master thread, runs
continuously from the beginning to the end of the program execution. The worker
threads are created for the duration of parallel regions and then are terminated.
When a programmer inserts a proper compiler directive, the system creates a
team of worker threads and distributes workload among them. This operation point
is called the fork. After forking, the program enters a parallel region where parallel
processing is done. After all threads finish their work, the program worker threads
cease to exist or are redeployed while the master thread continues its work. The
event of returning to the master thread is called the join. The fork and join tech-
nique may look like a simple and easy way for parallelizing sequential codes, but the
5
Figure 1.6: The fork and join OpenMP code flow.
reader should not be deceived. There are several difficulties that will be described
and discussed shortly. First of all, how to find whether several tasks can be run in
parallel?
In order for any group of computational tasks to be correctly executed in parallel,
they have to be independent. That means that the result of the computation does not
depend on the order of task execution. Formally, two programs or parts of programs
are independent if they satisfy the Bernstein conditions shown in 1.1.
I j ∩ Oi = 0
Ii ∩ Oj = 0 (1.1)
Oi ∩ O j = 0
Letters I and O signify input and output. The ∩ symbol means the intersection
of two sets that belong to task i or j. In practice, determining if some group of
tasks can be executed in parallel has to be done by the programmer and may not
be very easy. To discuss other shared memory computing issues, consider a very
simple programming task. Suppose the dot product of two large vectors x and y
whose number of components is n is calculated. The sequential computation would
be accomplished by a simple C loop
1 double dp = 0;
2 for(int i=0; i<n; i++)
3 dp += x[i]*y[i];
Inserting, in front of the above for-loop, the OpenMP directive for parallelizing
the loop and declaring shared and private variables leads to:
6
1 double dp = 0;
2 #pragma omp parallel for shared(x,y,dp) (private i)
3 for(int i=0; i<n; i++)
4 dp += x[i]*y[i];
1 double dp = 0;
2 #pragma omp parallel for shared(x,y,dp) private(i)
3 for(int i=0; i<n; i++){
4 #pragma omp critical
5 dp += x[i]*y[i];
6 }
1 double dp = 0;
2 #pragma omp parallel for reduction(+:dp) shared(x,y) private(i)
3 for (int i=0;i<n ;i++)
4 dp += x[i]*y[i];
Use of the critical construct amounts to serializing the computation of the critical
region, the block of code following the critical construct. For this reason, large critical
regions degrade program performance and ought to be avoided. Using the reduction
clause is more efficient and preferable. The reader may have noticed that the loop
counter variable i is declared private, so each thread updates its loop counter in-
dependently without interference. In addition to the parallelizing construct parallel
for that applies to C for-loops, there is a more general section construct that paral-
lelizes independent sections of code. The section construct makes it possible to apply
OpenMP to task parallelism where several threads can compute different sections of
a code. For computing two parallel sections, the code structure is:
7
1 #pragma omp parallel
2 {
3 #pragma omp sections
4 {
5 #pragma omp section
6 /* some program segment computation */
7 #pragma omp section
8 /* another program segment computation */
9 }
10 /* end of sections block */
11 }
12 /* end of parallel region */
OpenMP has tools that can be used by programmers for improving performance.
One of them is the clause nowait. Consider the program fragment:
In the first parallelized loop, there is the clause nowait. Since the second loop
variables do not depend on the results of the first loop, it is possible to use the clause
nowait – telling the compiler that as soon as any first loop thread finishes its work, it
can start doing the second loop work without waiting for other threads. This speeds
up computation by reducing the potential waiting time for the threads that finish
work at different times.
On the other hand, the construct #pragma omp barrier is inserted after the
second loop to make sure that the second loop is fully completed before the calcula-
tion of g being a function of d is performed after the second loop. At the barrier, all
threads computing the second loop must wait for the last thread to finish before they
proceed. In addition to the explicit barrier construct #pragma omp barrier used by
the programmer, there are also implicit barriers used by OpenMP automatically at
the end of every parallel region for the purpose of thread synchronization. To sum
up, barriers should be used sparingly and only if necessary. nowait clauses should
be used as frequently as possible, provided that their use is safe.
Finally, we have to point out that the major performance issue in numerical com-
putation is the use of cache memories. For example, in computing the matrix/vector
product c = Ab, two mathematically equivalent methods could be used. In the first
method, elements of the vector c are computed by multiplying each row of A by the
vector b, i.e., computing dot products. An inferior performance will be obtained if c
is computed as the sum of the columns of A multiplied by the elements of b. In this
8
case, the program would access the columns of A not in the way the matrix data are
stored in the main memory and transferred to caches.
For further in-depth study of OpenMP and its performance, reading [2] is highly
recommended. Of special value for parallel computing practitioners are Chapter 5
“How to get Good Performance by Using OpenMP” and Chapter 6 “Using OpenMP
in the Real World”. Chapter 6 offers advice for and against using combined MPI and
OpenMP. A chapter on combining MPI and OpenMP can also be found in [3]. Like
MPI and OpenMP, the OpenCL system is standardized. It has been designed to run
regardless of processor types, operating systems and memories. This makes OpenCL
programs highly portable, but the method of developing OpenCL codes is more com-
plicated. The OpenCL programmer has to deal with several low-level programming
issues, including memory management.
9
their own data sets. Task parallelism can be called Multiple Programs Multiple Data
(MPMD). A small-size example of a task parallel problem is shown in Fig. 1.8. The
directed graph indicates the task execution precedence. Two tasks can execute in
parallel if they are not dependent.
In the case shown in Fig. 1.8, there are two options for executing the entire
set of tasks. Option 1. Execute tasks T1, T2 and T4 in parallel, followed by Task T3
and finally T5. Option 2. Execute tasks T1 and T2 in parallel, then T3 and T4 in
parallel and finally T5. In both cases, the total computing work is equal but the time
to solution may not be.
1.2.3. Example
Consider a problem that can be computed in both ways – via data parallelism and
task parallelism. The problem is to calculate C = A × B − (D + E) where A, B, D
and E are all square matrices of size n × n. An obvious task parallel version would
be to compute in parallel two tasks A × B and D + E and then subtract the sum
from the product. Of course, the task of computing A × B and the task of computing
the sum D + E can be calculated in a data parallel fashion. Here, there are two
levels of parallelism: the higher task level and the lower data parallel level. Similar
multilevel parallelism is common in real-world applications. Not surprisingly, there
is also for this problem a direct data parallel method based on the observation that
every element of C can be computed directly and independently from the coefficients
of A, B, D and E. This computation is shown in equation 1.2.
n−1
ci j = aik bk j − di j − ei j (1.2)
k=0
10
The equation (1.2) means that it is possible to calculate all n2 elements of C
in parallel using the same formula. This is good news for OpenCL devices that can
handle only one kernel and related data parallel computation. Those devices will
be discussed in the chapters that follow. The matrix C can be computed using three
standard programming systems: MPI, OpenMP and OpenCL.
Using MPI, the matrix C could be partitioned into sub-matrix components and
assigned the subcomponents to processes. Every process would compute a set of
elements of C, using the expression 1.2. The main concern here is not computation
itself but the ease of assembling results and minimizing the cost of communication
while distributing data and assembling the results. A reasonable partitioning would
be dividing C by blocks of rows that can be scattered and gathered as user-defined
data types. Each process would get a set of the rows of A, D and E and the entire
matrix B. If matrix C size is n and the number of processes is p, then each process
would get n/p rows of C to compute. If a new data type is defined as n/p rows,
the data can easily be distributed by strips of rows to processes and then results can
be gathered. The suggested algorithm is a data parallel method. Data partitioning is
shown in Fig. 1.9.
Figure 1.9: Strips of data needed by a process to compute the topmost strip of C.
The data needed for each process include one strip of A, D and E and the entire
matrix B. Each process of rank 0<=rank<p computes one strip of C rows. After fin-
ishing computation, the matrix C can be assembled by the collective MPI communi-
cation function Gather. An alternative approach would be partitioning Gather into
blocks and assigning to each process computing one block of C. However, assembling
the results would be harder than in the strip partitioning case.
OpenMP would take advantage of the task parallel approach. First, the subtasks
A × B and D + E would be parallelized and computed separately, and then C = A ×
B − (D + E) would be computed, as shown in Fig. 1.10. One weakness of task parallel
programs is the efficiency loss if all parallel tasks do not represent equal workloads.
For example, in the matrix C computation, the task of computing A×B and the task of
computing D+E are not load-equal. Unequal workloads could cause some threads to
idle unless tasks are selected dynamically for execution by the scheduler. In general,
data parallel implementations are well load balanced and tend to be more efficient.
In the case of OpenCL implementation of the data parallel option for computing C, a
single kernel function would compute elements of C by the equation 1.2 where the
sum represents dot products of A × B and the remaining terms represent subtracting
D and E. If the matrix size is n = 1024, a compute device could execute over one
million work items in parallel. Additional performance gain can be achieved by using
the tiling technique [4].
11
Discovering Diverse Content Through
Random Scribd Documents
Teniendo en cuenta estas especiales circunstancias, la comparación
entre ambas mujeres daba nuevo incentivo a la lectura, y sobre el
sentimiento de indulgencia se agregaba el de la compasión con
cierto viso de cariño hacia la pobre muerta, parte de cuya herencia
era aquel libro. Es cierto que Manón expiró en un desierto, pero fué
en brazos del hombre que la amaba con todo el ardor de un alma
virgen, que la abrió una fosa regándola con sus lágrimas, y enterró
su corazón con el cuerpo de su adorada; mientras que Margarita,
pecadora como Manón y regenerada tal vez como ella, había
fallecido en medio del lujo, a juzgar por lo que yo acababa de ver, en
el lecho de su pasado, es cierto, pero también en medio del vacío
arenal de su corazón, más árido, más vasto, y mucho más horrible
que el en que fué enterrada Manón.
Algunos amigos, enterados de las últimas circunstancias de la vida
de Margarita, me contaron que a la cabecera de su cama no se
sentó ni una persona para consolarla en los dos meses largos que
duró su triste y dolorosa agonía.
Después de Manón y de Margarita mi pensamiento se dirigía a otras
que yo conocía y veía caminar alegres y contentas hacia una muerte
casi siempre igual.
¡Desgraciadas criaturas! si es delito el amarlas, es casi un deber
compadecerlas. Si compadecemos al ciego que jamás ha visto la luz
del sol, al sordo que jamás ha oído las armonías de la Naturaleza y
al mudo que jamás ha podido exhalar la voz de su alma, ¿por qué,
pues, bajo un falso pretexto de pudor, no hemos de compadecer
esta ceguera del corazón, esta sordera del alma, esta mudez de la
conciencia que vuelven loca a la infeliz que afligen, inhabilitándola
para ver el bien, sentir a Dios y hablar el casto y santo lenguaje del
amor y de la fe?
Hugo nos pintó Marion Delorme, de Musset Bernedette, Alejandro
Dumas Fernanda; los pensadores y poetas de todos los tiempos han
tributado a la desgraciada cortesana la ofrenda de su misericordia, y
ha habido grandes hombres que las han rehabilitado con su amor y
hasta con su nombre.
Mi insistencia sobre este punto es porque, entre los que van a
leerme, los puede haber resueltos a arrojar este libro, por el temor
de ver únicamente la apología del vicio y de la prostitución, y porque
tal vez la edad del autor puede contribuir a motivar tamaños recelos.
No teman los que esto supongan y continúen leyendo si ello sólo les
detiene.
Yo estoy altamente convencido de un principio, y es éste: A la mujer
que ignora el bien por falta de educación, Dios acostumbra trazarle
dos senderos que conducen únicamente a él: el dolor y el amor,
cuyo paso es bien difícil por cierto. Las que los siguen se
ensangrientan los pies y se lastiman las manos, pero al mismo
tiempo dejan en los abrojos del camino las galas del vicio, y llegan al
término con esa desnudez de que nadie se sonroja delante del
Señor.
Los que se encuentran con esas atrevidas viajeras, vienen obligados
a defenderlas, y decir a todo el mundo que las han encontrado,
puesto que éste es el modo más breve de enseñar la verdadera
senda.
Esto no quiere decir que se trate de colocar buenamente dos postes
a la entrada de la vida, con estas inscripciones: Senda del bien, y
Senda del mal, diciendo a los que se presenten: Elegid; sino que,
imitando a Jesús, debemos enseñar los atajos que conducen de la
segunda a la primera senda, a los que se dejaron seducir por la
amenidad de los alrededores, y sobre todo, se debe procurar que el
principio de estas veredas no sea muy escabroso, ni pueda
parecerles del todo impenetrable.
La maravillosa parábola del hijo pródigo preceptúa la indulgencia y el
perdón. Jesús prefería en su amor esas almas heridas por las
pasiones humanas, cuyas llagas se complacía en curar, sacando de
ellas mismas el remedio de salvación, cuando dijo a la Magdalena:
«Mucho se te ha de perdonar, porque has amado mucho». ¡Sublime
perdón que debía despertar una fe santa!
¿Y nosotros hemos de ser más severos que Jesús? ¿Por qué,
abroquelándonos en las opiniones de un mundo que petrifica su
sensibilidad para que se le crea fuerte, hemos de apartarnos de las
almas heridas que, con la sangre corrompida que de ellas mana,
arrastra la corrupción de su vida pasada? ¿Por qué hemos de
rechazar esas enfermedades sociales que sólo esperan una mano
amiga que las cure y les devuelva la paz del corazón?
A mi generación apelo, a las personas para quienes felizmente ya no
existen las teorías volterianas, a las que, como yo, creen que la
humanidad ha emprendido desde hace quince años una de sus más
atrevidas jornadas. Poseemos la ciencia del bien y del mal; y si el
mundo no se ha vuelto completamente bueno, al menos ha
mejorado en tercio y quinto.
Todos los hombres inteligentes dirígense al mismo fin, y todos los
grandes corazones se les adhieren; seamos buenos, seamos justos,
seamos veraces. El mal no es más que una vanidad; tengamos el
orgullo del bien, y sobre todo no desesperemos. No menospreciemos
a la mujer que no es madre, ni hija, ni esposa, ni hermana. No
reduzcamos el afecto al limitado círculo de la familia, ni vistamos el
egoísmo de indulgencia.
Una vez que el cielo gusta más del arrepentimiento de un pecador,
que de la oración de cien justos, procuremos que el cielo se regocije,
y busquemos en la satisfacción de hacer el bien, su propia
compensación.
Demos la limosna del perdón a las víctimas de los deseos terrenales,
a quienes salvará, tal vez, la esperanza de un más allá; y como dicen
las bondadosas ancianas cuando aconsejan un remedio casero, «si
no cura, tampoco hace daño».
Acaso alguien me tache de temerario, porque deseo obtener tan
grandes frutos del pequeño raigón que pretendo cultivar; pero yo
me cuento en el número de los que creen que lo máximo está en lo
mínimo. El niño es pequeño y encierra al hombre; el cerebro
estrecho, y abriga el pensamiento; el ojo es un punto y abarca
grandísimos espacios.
CAPÍTULO IV
A los dos días terminó la venta, que produjo ciento cincuenta mil
francos.
Dos terceras partes de la suma fueron para los acreedores, y la
familia, compuesta de una hermana y un sobrino, heredó el resto.
La hermana se quedó como quien ve visiones cuando el agente de
negocios le anunció que heredaba cincuenta mil francos. Hacía siete
años que la joven no había visto a su hermana mayor, la cual había
desaparecido de su casa un día sin que por nadie se averiguase el
menor detalle de su vida, desde el día en que se fué.
Faltóle tiempo para venir a París, y encontró su fortuna hecha y
derecha, sin querer averiguar el origen de tan inesperada riqueza.
Más tarde se me dijo que había vuelto a sus hogares con el corazón
lacerado por la muerte de la hermana, pero bastante consolada por
haber podido colocar la cantidad heredada al cuatro y medio por
ciento de interés.
Estas circunstancias, repetidas en París, población madre del
escándalo, empezaban a caer en el olvido, y ni yo mismo casi
recordaba la parte que tomé en tales sucesos, cuando otro incidente
casual me dió a conocer toda la historia de Margarita, enterándome
de tan interesantes pormenores, que me entraron deseos de
escribirla.
A los tres o cuatro días, estaban vendidos los muebles y la
habitación estaba por alquilar.
Una mañana llamaron a la puerta de mi casa. Mi portero, que hacía
las veces de criado, fué a abrir y me trajo una tarjeta, diciéndome
que la persona que se la había entregado quería hablarme.
Leí en la tarjeta estas palabras:
Armando Duval.
El nombre no me era desconocido, y en efecto, recordé el de la
primera página del volumen de Manón a Margarita.
¿Qué podía solicitar de mí la persona que había regalado el libro a
Margarita? Mandé que le hicieran pasar.
Era un joven rubio, alto, pálido, en traje de camino, que parecía no
habérselo quitado de encima desde algunos días; ni siquiera se lo
había cepillado a su llegada a París, pues venía cubierto de polvo.
El señor Duval, profundamente conmovido, no hizo ningún esfuerzo
para ocultar su emoción, y arrasados los ojos, me dijo con voz
entrecortada:
—Caballero, os suplico me perdonéis por veniros a visitar en
semejante traje. Entre jóvenes se suprimen fácilmente ciertas
formalidades. Y luego, era tan vivo el deseo por veros hoy mismo,
que ni siquiera me tomé tiempo para instalarme en la fonda, a
donde mandé mi equipaje, volando a vuestra casa, temeroso de no
encontraros a pesar de ser tan de mañana.
Rogué al señor Duval que se sirviese tomar asiento cerca de la
chimenea, lo que efectuó sacando un pañuelo con el cual ocultó su
rostro por unos momentos.
—No vais a adivinar—dijo sonriendo tristemente,—el por qué viene
este desconocido a visitaros, a tal hora con semejante traje y
llorando como un chiquillo. Me he permitido venir a pediros un gran
servicio.
—Hablad, caballero. Estoy a vuestras órdenes.
—¿Asististeis a la venta de los muebles de Margarita Gautier?
Al pronunciar este nombre, la emoción de que el joven parecía haber
triunfado, fué más poderosa que él, y tuvo que enjugar nuevas
lágrimas.
—Debo pareceros bastante ridículo—añadió;—perdonadme, amigo
mío, y creed que nunca olvidaré la paciencia con que tenéis la
bondad de atenderme.
—Caballero—repliqué,—si el servicio que según decís puedo
prestaros ha de mitigar algún tanto el dolor que hiere vuestra alma,
sepa yo en qué puedo complaceros y tened la seguridad de que me
consideraré dichoso si llego a satisfaceros.
La aflicción del señor Duval era simpática, y a pesar mío, hubiera
deseado poderle servir.
Entonces me interrogó diciendo:
—¿Habéis comprado algo en la venta de los objetos de la pobre
Margarita?
—Sí, señor; un libro.
—¿Manon Lescaut?
—Efectivamente.
—¿Lo tenéis aún?
—En mi cuarto.
La noticia pareció aliviarle de un gran peso, y me dió las gracias,
como si yo hubiese ya empezado a prestarle el servicio con tener a
mano aquel volumen.
Levantéme, entré en mi gabinete, tomé el libro y lo puse en su
mano.
—El mismo—exclamó mirando la dedicatoria de la primera página y
hojeándolo;—sí, éste es.
Dos grandes lágrimas rodaron por la superficie del libro.
—Caballero—dijo levantando la cabeza y sin tratar de ocultarme que
había llorado y estaba dispuesto a continuar:—¿os interesa mucho
este libro?
—¿Por qué, caballero?
—Porque vengo a suplicaros encarecidamente que me lo cedáis.
—Perdonad mi curiosidad—dije entonces;—pero, según eso, ¿sois
vos quién lo regaló a Margarita Gautier?
—Sí, señor.
—Recobradle, amigo mío, me alegro de ser yo quien os lo devuelva.
—Pero—prosiguió el señor Duval algo turbado,—es justo que al
menos os reembolséis lo que os costó.
—Permitidme que os lo ofrezca. El precio de un solo libro en
semejante venta es bien insignificante y ya ni siquiera lo recuerdo.
—Cien francos.
—Es verdad—dije turbándome a mi vez;—¿cómo lo sabéis?
—Es muy sencillo: yo creía estar a tiempo para la venta, y no he
podido llegar hasta hoy. Deseaba poseer un objeto cualquiera de
Margarita, y me dirigí a casa del tasador para pedirle que me dejara
ver la lista de los muebles vendidos y de los nombres de los
compradores. Vi que habíais comprado este libro y resolví suplicaros
que me lo cedieseis, aunque el precio a que lo pagasteis infundiese
en mí cierto recelo sobre la causa de vuestra adquisición.
Y así diciendo, parecía temer que yo hubiese conocido a Margarita
hasta el punto que él la conociera. Me apresuré a tranquilizarle.
—La conocí de vista—le dije;—su muerte me causó la impresión que
siempre causa a un joven la muerte de una mujer hermosa a quien
se alegraba de encontrar. Quise comprar alguna cosa al venderse
sus muebles; y me encapriché pujando sobre este libro, por el gusto
de hacer rabiar a un pobre diablo que se obstinaba en pagarlo más
caro que yo. Repítoos, pues, caballero, que el libro está a vuestra
disposición, y os ruego que lo aceptéis y no lo recibáis de mí como
yo lo recibí del tasador, pues de este modo puede ser el lazo de una
amistad que me complazco en ofreceros.
—Está bien, amigo mío—dijo Armando tendiéndome la mano y
apretando la mía.—Acepto, y creed que mi agradecimiento será
eterno.
Yo tenía grandes deseos de interrogar a Armando respecto de
Margarita, pues aquella dedicatoria del libro, su viaje y el deseo de
poseer aquel volumen aumentaban mi curiosidad; pero temí que de
mis preguntas pudiese colegir que rehusaba su dinero para tener el
derecho de inmiscuirme en sus asuntos, lo cual no entraba en mis
cálculos.
Hubiérase dicho que adivinó mi deseo, pues me dijo:
—¿Habéis leído este libro?
—Sí, señor.
—¿Qué pensasteis al ver las dos líneas que escribí en él?
—Supuse que, a vuestro modo de entender, la pobre joven a quien
regalasteis este volumen se separaba de la categoría ordinaria; pues
no quise ver en estas dos líneas un cumplimiento vulgar.
—Supusisteis bien, caballero. ¡Era un ángel! Tomad,—me dijo,—leed
esta carta.
Y me entregó un papel que parecía haber sido leído repetidas veces.
Lo abrí, y leí estas palabras:
«M¡ querido Armando: Recibí vuestra carta, gozáis de
buena salud, y doy gracias a Dios, porque os concede tal
beneficio.
«Sí, amigo mío, estoy enferma, y mi enfermedad no tiene
cura; pero el interés que os dignáis tomar por mí alivia
mucho mis sufrimientos. Sin duda no viviré el tiempo
indispensable para tener la dicha de estrechar la mano
que ha escrito la bondadosa carta que acabo de recibir, y
cuyas palabras me curarían si algo pudiese curarme. No
creo volveros a ver, pues me encuentro al borde de la
tumba, y me separa de vos una distancia incalculable.
«¡Pobre amigo mío! vuestra Margarita de otros tiempos ha
cambiado por completo, y me parece preferible que no
volváis a verla, si habéis de encontrarla tal como está.
¿Me preguntáis si os perdono? ¡oh! de todo corazón,
amigo mío, pues el mal que habéis querido hacerme no
era más que una prueba de verdadero amor. Hace un mes
que no he dejado el lecho, y me es tan cara vuestra
estimación, que, desde el instante en que nos separamos,
escribo el diario de mi vida y seguiré haciéndolo hasta que
mi mano se niegue a sostener la pluma. Si el interés que
por mí manifestáis es verdadero, Armando, os suplico que
cuando volváis, veáis a Julia Duprat, que os entregará
este diario. Por él sabréis la razón y la causa de cuanto ha
ocurrido entre nosotros. Julia es muy buena, y con
frecuencia me habla de vos. Se encontraba aquí cuando
recibí vuestra carta, y hemos llorado juntas leyéndola.
«Si hubieseis dejado de darme noticias, Julia quedaba
encargada de entregaros estos papeles a vuestra llegada
a Francia. No me lo agradezcáis. Este recuerdo diario de
los únicos momentos felices de mi vida me hace un gran
bien, y si en su lectura debéis vos hallar las excusas del
pasado, a mí me ofrece un bálsamo de consuelo
inagotable.
«Desearía dejaros algún recuerdo que os hiciese pensar
constantemente en mí, pero han embargado mis muebles,
y nada me pertenece ya.
«¿Comprendéis, amigo mío? Se acerca mi muerte, y desde
mi alcoba escucho los graves pasos del vigilante que mis
acreedores han puesto en el salón para evitar que nadie
se lleve nada. Con seguridad aguardan mi fallecimiento
para proceder a la venta de lo embargado.
«¡Oh! ¡los hombres no tienen piedad! Pero me engaño: el
justo, el inflexible, es Dios.
«Y bien, querido amigo, espero que cuando se realice la
venta, compraréis algo, pues si retirase cualquier objeto
para vos y lo supieran, serían capaces de acusaros de
sustractor de efectos embargados.
«¡Cuán triste es la vida que abandono!
«¡Qué bueno sería Dios si consintiese que nos viésemos
antes de yo expirar!
«Creo deber despedirme de vos según todas las
probabilidades, ¡adiós, pues, amigo mío! perdonadme si
no prolongo esta carta, porque los que se proponen
curarme me debilitan a fuerza de sangrías, y mi mano se
niega a seguir escribiendo.
«Margarita Gautier».
Las últimas palabras casi no podían leerse.
Devolví la carta a Armando, que sin duda acababa de leerla también
en su pensamiento, como yo en el papel, pues al tomarla exclamó:
—¡Quién diría que la que ha escrito estas líneas era una cortesana!
Y conmovido por los recuerdos, contempló por un momento el papel
y acabó por besarlo.
—¡Ah! cuando pienso—prosiguió,—que ha muerto sin que yo
pudiese volver a verla, que no la veré y que hizo por mí lo que no
hubiera hecho una hermana, no puedo perdonarme haberla dejado
morir de esta manera. ¡Muerta! ¡muerta! ¡pensando en mí,
escribiendo y pronunciando mi nombre! ¡desdichada Margarita!
Y Duval, dando rienda suelta a sus pensamientos y a sus lágrimas,
me tendió la mano y apretó la mía, continuando:
—Son muchos los que si me viesen lamentar así semejante muerte,
creeríanme un chiquillo; pero es porque ignorarían cuánto he hecho
sufrir a esta mujer, cuán cruel fuí y cuán buena y resignada fué ella.
Tuve la audacia de creer que a mí sólo me tocaba perdonar, y hoy
me considero indigno del perdón que ella me concede. ¡Oh! daría
diez años de mi vida por llorar a sus pies un solo momento.
Es casi imposible consolar un dolor que no se conoce, y sin
embargo, era tan viva la simpatía que me había inspirado aquel
joven, se me confiaba con tanta franqueza, que llegué a creer que
mis palabras no le serían indiferentes.
—¿No tenéis parientes o amigos?—le dije.—Vedles y os consolarán,
pues por mi parte sólo puedo compadeceros.
—Es cierto—dijo levantándose y paseándose agitado por la
habitación;—os molesto. Perdonadme, yo no reflexionaba que mis
penas deben importaros poco, y que os importuno por lo que no
puede o no debe inspiraros el menor interés.
—No me habéis comprendido; estoy a vuestra disposición, y sólo
deploro mi insuficiencia para calmar vuestra pena. Si mi compañía y
la de mis amigos puede distraeros, si necesitáis de mí, en cualquier
terreno que fuere, quiero que me dispenséis el placer de satisfacer
vuestros deseos.
—Perdonadme una y mil veces—me dijo;—el dolor exagera las
impresiones. Permitid que permanezca aquí algunos minutos más, el
tiempo de enjugarme los ojos, para que los bobos de la calle no
vean con curiosidad mis lágrimas. Me hacéis un gran bien dándome
este libro, y nunca sabré agradeceros tal favor. ¿Cómo pagároslo?
—Concediéndome vuestra amistad y explicándome el origen de
vuestro dolor—repuse.—¡Es tan consolador contar nuestros
sufrimientos!
—Es verdad, pero hoy no podría; siento necesidad de llorar, y mis
labios no podrían formular las palabras. Otro día os referiré tan triste
historia, y podréis apreciar cuán grandes son los motivos que tengo
para llorar su muerte. Por último—añadió pasando sus manos por los
ojos y mirándose en el espejo,—tened la bondad de decirme que no
me halláis demasiado simple, y permitidme que vuelva a visitaros.
—¡Valor, amigo mío, valor!—le dije.
Y haciendo esfuerzos inauditos para no llorar, mejor huyó que salió
de mi casa.
Desde el balcón le vi subir al carruaje que le esperaba: apenas entró
en él, se puso a llorar como un desesperado, tapándose la cara con
el pañuelo.
CAPÍTULO V
Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.
ebookname.com