100% found this document useful (4 votes)
17 views

PDF Data Parallel C++: Programming Accelerated Systems Using C++ and SYCL 2nd Edition James Reinders download

Data

Uploaded by

falihookawa
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (4 votes)
17 views

PDF Data Parallel C++: Programming Accelerated Systems Using C++ and SYCL 2nd Edition James Reinders download

Data

Uploaded by

falihookawa
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 76

Download Full Version ebookmass - Visit ebookmass.

com

Data Parallel C++: Programming Accelerated Systems


Using C++ and SYCL 2nd Edition James Reinders

https://ebookmass.com/product/data-parallel-c-programming-
accelerated-systems-using-c-and-sycl-2nd-edition-james-
reinders/

OR CLICK HERE

DOWLOAD NOW

Discover More Ebook - Explore Now at ebookmass.com


Instant digital products (PDF, ePub, MOBI) ready for you
Download now and discover formats that fit your needs...

Data Parallel C++: Programming Accelerated Systems Using


C++ and SYCL 2nd Edition James Reinders

https://ebookmass.com/product/data-parallel-c-programming-accelerated-
systems-using-c-and-sycl-2nd-edition-james-reinders/

ebookmass.com

Programming: Principles and Practice Using C++ 2nd


Edition, (Ebook PDF)

https://ebookmass.com/product/programming-principles-and-practice-
using-c-2nd-edition-ebook-pdf/

ebookmass.com

C Programming Language: C PROGRAMMING LANG _p2 2nd


Edition, (Ebook PDF)

https://ebookmass.com/product/c-programming-language-c-programming-
lang-_p2-2nd-edition-ebook-pdf/

ebookmass.com

La Parole Est une Force, Captiver, Emouvoir, Convaincre


Trevor Currie

https://ebookmass.com/product/la-parole-est-une-force-captiver-
emouvoir-convaincre-trevor-currie/

ebookmass.com
Egg Innovations and Strategies for Improvements Patricia
Hester

https://ebookmass.com/product/egg-innovations-and-strategies-for-
improvements-patricia-hester/

ebookmass.com

Lenny (Moon Burrow Bears Book 8) Mm Fel Fern

https://ebookmass.com/product/lenny-moon-burrow-bears-book-8-mm-fel-
fern/

ebookmass.com

The Ascent Adam Plantinga

https://ebookmass.com/product/the-ascent-adam-plantinga/

ebookmass.com

The Existential Crisis of Motherhood Claire Arnold-Baker

https://ebookmass.com/product/the-existential-crisis-of-motherhood-
claire-arnold-baker/

ebookmass.com

Burning Desire Marie Harte

https://ebookmass.com/product/burning-desire-marie-harte-2/

ebookmass.com
A Hundred Other Girls: A Novel Iman Hariri-Kia

https://ebookmass.com/product/a-hundred-other-girls-a-novel-iman-
hariri-kia/

ebookmass.com
Data Parallel C++
Programming Accelerated Systems Using
C++ and SYCL

Second Edition

James Reinders
Ben Ashbaugh
James Brodman
Michael Kinsner
John Pennycook
Xinmin Tian
Foreword by Erik Lindahl, GROMACS and
Stockholm University
Data Parallel C++
Programming Accelerated
Systems Using C++ and SYCL
Second Edition

James Reinders
Ben Ashbaugh
James Brodman
Michael Kinsner
John Pennycook
Xinmin Tian
Foreword by Erik Lindahl, GROMACS and
Stockholm University
Data Parallel C++: Programming Accelerated Systems Using C++ and SYCL, Second Edition
James Reinders Michael Kinsner
Beaverton, OR, USA Halifax, NS, Canada
Ben Ashbaugh John Pennycook
Folsom, CA, USA San Jose, CA, USA
James Brodman Xinmin Tian
Marlborough, MA, USA Fremont, CA, USA

ISBN-13 (pbk): 978-1-4842-9690-5 ISBN-13 (electronic): 978-1-4842-9691-2


https://doi.org/10.1007/978-1-4842-9691-2

Copyright © 2023 by Intel Corporation


This work is subject to copyright. All rights are reserved by the Publisher, whether the whole or part of the material is
concerned, specifically the rights of translation, reprinting, reuse of illustrations, recitation, broadcasting, reproduction on
microfilms or in any other physical way, and transmission or information storage and retrieval, electronic adaptation,
computer software, or by similar or dissimilar methodology now known or hereafter developed.
Open Access This book is licensed under the terms of the Creative Commons Attribution 4.0
International License (https://creativecommons.org/licenses/by/4.0/), which permits use, sharing,
adaptation, distribution and reproduction in any medium or format, as long as you give appropriate
credit to the original author(s) and the source, provide a link to the Creative Commons license and indicate if changes
were made.
The images or other third party material in this book are included in the book’s Creative Commons license, unless indicated
otherwise in a credit line to the material. If material is not included in the book’s Creative Commons license and your intended
use is not permitted by statutory regulation or exceeds the permitted use, you will need to obtain permission directly from the
copyright holder.
Trademarked names, logos, and images may appear in this book. Rather than use a trademark symbol with every occurrence of
a trademarked name, logo, or image we use the names, logos, and images only in an editorial fashion and to the benefit of the
trademark owner, with no intention of infringement of the trademark.
The use in this publication of trade names, trademarks, service marks, and similar terms, even if they are not identified as such,
is not to be taken as an expression of opinion as to whether or not they are subject to proprietary rights.
Intel, the Intel logo, Intel Optane, and Xeon are trademarks of Intel Corporation in the U.S. and/or other countries. OpenCL
and the OpenCL logo are trademarks of Apple Inc. in the U.S. and/or other countries. OpenMP and the OpenMP logo are
trademarks of the OpenMP Architecture Review Board in the U.S. and/or other countries. SYCL, the SYCL logo, Khronos and
the Khronos Group logo are trademarks of the Khronos Group Inc. The open source DPC++ compiler is based on a published
Khronos SYCL specification. The current conformance status of SYCL implementations can be found at https://www.khronos.
org/conformance/adopters/conformant-products/sycl.
Software and workloads used in performance tests may have been optimized for performance only on Intel microprocessors.
Performance tests are measured using specific computer systems, components, software, operations and functions. Any change
to any of those factors may cause the results to vary. You should consult other information and performance tests to assist you
in fully evaluating your contemplated purchases, including the performance of that product when combined with other
products. For more complete information visit https://www.intel.com/benchmarks. Performance results are based on testing
as of dates shown in configuration and may not reflect all publicly available security updates. See configuration disclosure for
details. No product or component can be absolutely secure. Intel technologies’ features and benefits depend on system
configuration and may require enabled hardware, software or service activation. Performance varies depending on system
configuration. No computer system can be absolutely secure. Check with your system manufacturer or retailer or learn more at
www.intel.com.
While the advice and information in this book are believed to be true and accurate at the date of publication, neither the
authors nor the editors nor the publisher can accept any legal responsibility for any errors or omissions that may be made. The
publisher makes no warranty, express or implied, with respect to the material contained herein.
Managing Director, Apress Media LLC: Welmoed Spahr
Acquisitions Editor: Susan McDermot
Development Editor: James Markham
Coordinating Editor: Jessica Vakili
Distributed to the book trade worldwide by Springer Science+Business Media New York, 1 NY Plaza, New York, NY 10004.
Phone 1-800-SPRINGER, fax (201) 348-4505, e-mail orders-ny@springer-sbm.com, or visit https://www.springeronline.com.
Apress Media, LLC is a California LLC and the sole member (owner) is Springer Science + Business Media Finance Inc (SSBM
Finance Inc). SSBM Finance Inc is a Delaware corporation.
For information on translations, please e-mail booktranslations@springernature.com; for reprint, paperback, or audio rights,
please e-mail bookpermissions@springernature.com.
Apress titles may be purchased in bulk for academic, corporate, or promotional use. eBook versions and licenses are also
available for most titles. For more information, reference our Print and eBook Bulk Sales web page at https://www.apress.com/
bulk-sales.
Any source code or other supplementary material referenced by the author in this book is available to readers on the Github
repository: https://github.com/Apress/Data-Parallel-CPP. For more detailed information, please visit https://www.apress.
com/gp/services/source-code.
Paper in this product is recyclable
Table of Contents
About the Authors������������������������������������������������������������������������������xix

Preface����������������������������������������������������������������������������������������������xxi

Foreword������������������������������������������������������������������������������������������ xxv

Acknowledgments��������������������������������������������������������������������������� xxix

Chapter 1: Introduction������������������������������������������������������������������������1
Read the Book, Not the Spec��������������������������������������������������������������������������������2
SYCL 2020 and DPC++�����������������������������������������������������������������������������������������3
Why Not CUDA?�����������������������������������������������������������������������������������������������������4
Why Standard C++ with SYCL?����������������������������������������������������������������������������5
Getting a C++ Compiler with SYCL Support���������������������������������������������������������5
Hello, World! and a SYCL Program Dissection�������������������������������������������������������6
Queues and Actions����������������������������������������������������������������������������������������������7
It Is All About Parallelism��������������������������������������������������������������������������������������8
Throughput������������������������������������������������������������������������������������������������������8
Latency������������������������������������������������������������������������������������������������������������9
Think Parallel���������������������������������������������������������������������������������������������������9
Amdahl and Gustafson����������������������������������������������������������������������������������10
Scaling�����������������������������������������������������������������������������������������������������������11
Heterogeneous Systems��������������������������������������������������������������������������������11
Data-Parallel Programming���������������������������������������������������������������������������13

iii
Table of Contents

Key Attributes of C++ with SYCL������������������������������������������������������������������������14


Single-Source������������������������������������������������������������������������������������������������14
Host���������������������������������������������������������������������������������������������������������������15
Devices����������������������������������������������������������������������������������������������������������15
Kernel Code���������������������������������������������������������������������������������������������������16
Asynchronous Execution�������������������������������������������������������������������������������18
Race Conditions When We Make a Mistake���������������������������������������������������19
Deadlock��������������������������������������������������������������������������������������������������������22
C++ Lambda Expressions�����������������������������������������������������������������������������23
Functional Portability and Performance Portability���������������������������������������26
Concurrency vs. Parallelism��������������������������������������������������������������������������������28
Summary������������������������������������������������������������������������������������������������������������30

Chapter 2: Where Code Executes��������������������������������������������������������31


Single-Source�����������������������������������������������������������������������������������������������������31
Host Code������������������������������������������������������������������������������������������������������33
Device Code���������������������������������������������������������������������������������������������������34
Choosing Devices������������������������������������������������������������������������������������������������36
Method#1: Run on a Device of Any Type�������������������������������������������������������������37
Queues����������������������������������������������������������������������������������������������������������37
Binding a Queue to a Device When Any Device Will Do���������������������������������41
Method#2: Using a CPU Device for Development, Debugging,
and Deployment��������������������������������������������������������������������������������������������������42
Method#3: Using a GPU (or Other Accelerators)��������������������������������������������������45
Accelerator Devices���������������������������������������������������������������������������������������46
Device Selectors��������������������������������������������������������������������������������������������46
Method#4: Using Multiple Devices����������������������������������������������������������������������50

iv
Table of Contents

Method#5: Custom (Very Specific) Device Selection������������������������������������������51


Selection Based on Device Aspects���������������������������������������������������������������51
Selection Through a Custom Selector�����������������������������������������������������������53
Creating Work on a Device����������������������������������������������������������������������������������54
Introducing the Task Graph����������������������������������������������������������������������������54
Where Is the Device Code?����������������������������������������������������������������������������56
Actions�����������������������������������������������������������������������������������������������������������60
Host tasks������������������������������������������������������������������������������������������������������63
Summary������������������������������������������������������������������������������������������������������������65

Chapter 3: Data Management�������������������������������������������������������������67


Introduction���������������������������������������������������������������������������������������������������������68
The Data Management Problem�������������������������������������������������������������������������69
Device Local vs. Device Remote�������������������������������������������������������������������������69
Managing Multiple Memories�����������������������������������������������������������������������������70
Explicit Data Movement���������������������������������������������������������������������������������70
Implicit Data Movement��������������������������������������������������������������������������������71
Selecting the Right Strategy��������������������������������������������������������������������������71
USM, Buffers, and Images�����������������������������������������������������������������������������������72
Unified Shared Memory��������������������������������������������������������������������������������������72
Accessing Memory Through Pointers������������������������������������������������������������73
USM and Data Movement������������������������������������������������������������������������������74
Buffers����������������������������������������������������������������������������������������������������������������77
Creating Buffers��������������������������������������������������������������������������������������������78
Accessing Buffers������������������������������������������������������������������������������������������78
Access Modes�����������������������������������������������������������������������������������������������80

v
Table of Contents

Ordering the Uses of Data�����������������������������������������������������������������������������������80


In-order Queues���������������������������������������������������������������������������������������������83
Out-of-Order Queues�������������������������������������������������������������������������������������84
Choosing a Data Management Strategy��������������������������������������������������������������92
Handler Class: Key Members������������������������������������������������������������������������������93
Summary������������������������������������������������������������������������������������������������������������96

Chapter 4: Expressing Parallelism������������������������������������������������������97


Parallelism Within Kernels����������������������������������������������������������������������������������98
Loops vs. Kernels������������������������������������������������������������������������������������������������99
Multidimensional Kernels���������������������������������������������������������������������������������101
Overview of Language Features�����������������������������������������������������������������������102
Separating Kernels from Host Code������������������������������������������������������������102
Different Forms of Parallel Kernels�������������������������������������������������������������������103
Basic Data-Parallel Kernels������������������������������������������������������������������������������105
Understanding Basic Data-Parallel Kernels�������������������������������������������������105
Writing Basic Data-Parallel Kernels������������������������������������������������������������107
Details of Basic Data-Parallel Kernels���������������������������������������������������������109
Explicit ND-Range Kernels��������������������������������������������������������������������������������112
Understanding Explicit ND-Range Parallel Kernels�������������������������������������113
Writing Explicit ND-Range Data-Parallel Kernels����������������������������������������121
Details of Explicit ND-Range Data-­Parallel Kernels�������������������������������������122
Mapping Computation to Work-Items���������������������������������������������������������������127
One-to-One Mapping�����������������������������������������������������������������������������������128
Many-to-One Mapping���������������������������������������������������������������������������������128
Choosing a Kernel Form������������������������������������������������������������������������������������130
Summary����������������������������������������������������������������������������������������������������������132

vi
Table of Contents

Chapter 5: Error Handling�����������������������������������������������������������������135


Safety First��������������������������������������������������������������������������������������������������������135
Types of Errors��������������������������������������������������������������������������������������������������136
Let’s Create Some Errors!���������������������������������������������������������������������������������138
Synchronous Error���������������������������������������������������������������������������������������139
Asynchronous Error�������������������������������������������������������������������������������������139
Application Error Handling Strategy������������������������������������������������������������������140
Ignoring Error Handling�������������������������������������������������������������������������������141
Synchronous Error Handling������������������������������������������������������������������������143
Asynchronous Error Handling����������������������������������������������������������������������144
The Asynchronous Handler��������������������������������������������������������������������������145
Invocation of the Handler����������������������������������������������������������������������������148
Errors on a Device���������������������������������������������������������������������������������������������149
Summary����������������������������������������������������������������������������������������������������������150

Chapter 6: Unified Shared Memory���������������������������������������������������153


Why Should We Use USM?��������������������������������������������������������������������������������153
Allocation Types������������������������������������������������������������������������������������������������154
Device Allocations���������������������������������������������������������������������������������������154
Host Allocations�������������������������������������������������������������������������������������������155
Shared Allocations���������������������������������������������������������������������������������������155
Allocating Memory��������������������������������������������������������������������������������������������156
What Do We Need to Know?������������������������������������������������������������������������156
Multiple Styles���������������������������������������������������������������������������������������������157
Deallocating Memory����������������������������������������������������������������������������������164
Allocation Example��������������������������������������������������������������������������������������165

vii
Table of Contents

Data Management���������������������������������������������������������������������������������������������165
Initialization�������������������������������������������������������������������������������������������������165
Data Movement�������������������������������������������������������������������������������������������166
Queries��������������������������������������������������������������������������������������������������������������174
One More Thing�������������������������������������������������������������������������������������������������177
Summary����������������������������������������������������������������������������������������������������������178

Chapter 7: Buffers����������������������������������������������������������������������������179
Buffers��������������������������������������������������������������������������������������������������������������180
Buffer Creation��������������������������������������������������������������������������������������������181
What Can We Do with a Buffer?������������������������������������������������������������������188
Accessors����������������������������������������������������������������������������������������������������������189
Accessor Creation���������������������������������������������������������������������������������������192
What Can We Do with an Accessor?������������������������������������������������������������198
Summary����������������������������������������������������������������������������������������������������������199

Chapter 8: Scheduling Kernels and Data Movement������������������������201


What Is Graph Scheduling?�������������������������������������������������������������������������������202
How Graphs Work in SYCL��������������������������������������������������������������������������������202
Command Group Actions�����������������������������������������������������������������������������203
How Command Groups Declare Dependences��������������������������������������������203
Examples�����������������������������������������������������������������������������������������������������204
When Are the Parts of a Command Group Executed?����������������������������������213
Data Movement�������������������������������������������������������������������������������������������������213
Explicit Data Movement�������������������������������������������������������������������������������213
Implicit Data Movement������������������������������������������������������������������������������214
Synchronizing with the Host�����������������������������������������������������������������������������216
Summary����������������������������������������������������������������������������������������������������������218

viii
Table of Contents

Chapter 9: Communication and Synchronization�����������������������������221


Work-Groups and Work-Items���������������������������������������������������������������������������221
Building Blocks for Efficient Communication����������������������������������������������������223
Synchronization via Barriers�����������������������������������������������������������������������223
Work-Group Local Memory��������������������������������������������������������������������������225
Using Work-Group Barriers and Local Memory�������������������������������������������������227
Work-Group Barriers and Local Memory in ND-Range Kernels�������������������231
Sub-Groups�������������������������������������������������������������������������������������������������������235
Synchronization via Sub-Group Barriers�����������������������������������������������������236
Exchanging Data Within a Sub-Group����������������������������������������������������������237
A Full Sub-Group ND-Range Kernel Example����������������������������������������������239
Group Functions and Group Algorithms������������������������������������������������������������241
Broadcast����������������������������������������������������������������������������������������������������241
Votes������������������������������������������������������������������������������������������������������������242
Shuffles�������������������������������������������������������������������������������������������������������243
Summary����������������������������������������������������������������������������������������������������������246

Chapter 10: Defining Kernels������������������������������������������������������������249


Why Three Ways to Represent a Kernel?����������������������������������������������������������249
Kernels as Lambda Expressions�����������������������������������������������������������������������251
Elements of a Kernel Lambda Expression���������������������������������������������������251
Identifying Kernel Lambda Expressions������������������������������������������������������254
Kernels as Named Function Objects�����������������������������������������������������������������255
Elements of a Kernel Named Function Object���������������������������������������������256
Kernels in Kernel Bundles���������������������������������������������������������������������������������259
Interoperability with Other APIs������������������������������������������������������������������������264
Summary����������������������������������������������������������������������������������������������������������264

ix
Table of Contents

Chapter 11: Vectors and Math Arrays����������������������������������������������267


The Ambiguity of Vector Types��������������������������������������������������������������������������268
Our Mental Model for SYCL Vector Types����������������������������������������������������������269
Math Array (marray)������������������������������������������������������������������������������������������271
Vector (vec)�������������������������������������������������������������������������������������������������������273
Loads and Stores�����������������������������������������������������������������������������������������274
Interoperability with Backend-Native Vector Types�������������������������������������276
Swizzle Operations��������������������������������������������������������������������������������������276
How Vector Types Execute��������������������������������������������������������������������������������280
Vectors as Convenience Types��������������������������������������������������������������������280
Vectors as SIMD Types��������������������������������������������������������������������������������284
Summary����������������������������������������������������������������������������������������������������������286

Chapter 12: Device Information and Kernel Specialization��������������289


Is There a GPU Present?������������������������������������������������������������������������������������290
Refining Kernel Code to Be More Prescriptive��������������������������������������������������291
How to Enumerate Devices and Capabilities����������������������������������������������������293
Aspects��������������������������������������������������������������������������������������������������������296
Custom Device Selector������������������������������������������������������������������������������298
Being Curious: get_info<>��������������������������������������������������������������������������300
Being More Curious: Detailed Enumeration Code����������������������������������������301
Very Curious: get_info plus has()�����������������������������������������������������������������303
Device Information Descriptors������������������������������������������������������������������������303
Device-Specific Kernel Information Descriptors�����������������������������������������������303
The Specifics: Those of “Correctness”��������������������������������������������������������������304
Device Queries��������������������������������������������������������������������������������������������305
Kernel Queries���������������������������������������������������������������������������������������������306

x
Table of Contents

The Specifics: Those of “Tuning/Optimization”�������������������������������������������������307


Device Queries��������������������������������������������������������������������������������������������307
Kernel Queries���������������������������������������������������������������������������������������������308
Runtime vs. Compile-Time Properties��������������������������������������������������������������308
Kernel Specialization����������������������������������������������������������������������������������������309
Summary����������������������������������������������������������������������������������������������������������312

Chapter 13: Practical Tips����������������������������������������������������������������313


Getting the Code Samples and a Compiler�������������������������������������������������������313
Online Resources����������������������������������������������������������������������������������������������313
Platform Model�������������������������������������������������������������������������������������������������314
Multiarchitecture Binaries���������������������������������������������������������������������������315
Compilation Model���������������������������������������������������������������������������������������316
Contexts: Important Things to Know�����������������������������������������������������������������319
Adding SYCL to Existing C++ Programs�����������������������������������������������������������321
Considerations When Using Multiple Compilers�����������������������������������������������322
Debugging���������������������������������������������������������������������������������������������������������323
Debugging Deadlock and Other Synchronization Issues�����������������������������325
Debugging Kernel Code�������������������������������������������������������������������������������326
Debugging Runtime Failures�����������������������������������������������������������������������327
Queue Profiling and Resulting Timing Capabilities��������������������������������������330
Tracing and Profiling Tools Interfaces����������������������������������������������������������334
Initializing Data and Accessing Kernel Outputs������������������������������������������������335
Multiple Translation Units����������������������������������������������������������������������������������344
Performance Implication of Multiple Translation Units��������������������������������345
When Anonymous Lambdas Need Names��������������������������������������������������������345
Summary����������������������������������������������������������������������������������������������������������346

xi
Table of Contents

Chapter 14: Common Parallel Patterns���������������������������������������������349


Understanding the Patterns������������������������������������������������������������������������������350
Map�������������������������������������������������������������������������������������������������������������351
Stencil���������������������������������������������������������������������������������������������������������352
Reduction����������������������������������������������������������������������������������������������������354
Scan������������������������������������������������������������������������������������������������������������356
Pack and Unpack�����������������������������������������������������������������������������������������358
Using Built-In Functions and Libraries��������������������������������������������������������������360
The SYCL Reduction Library������������������������������������������������������������������������360
Group Algorithms�����������������������������������������������������������������������������������������366
Direct Programming������������������������������������������������������������������������������������������370
Map�������������������������������������������������������������������������������������������������������������370
Stencil���������������������������������������������������������������������������������������������������������371
Reduction����������������������������������������������������������������������������������������������������373
Scan������������������������������������������������������������������������������������������������������������374
Pack and Unpack�����������������������������������������������������������������������������������������377
Summary����������������������������������������������������������������������������������������������������������380
For More Information�����������������������������������������������������������������������������������381

Chapter 15: Programming for GPUs��������������������������������������������������383


Performance Caveats����������������������������������������������������������������������������������������383
How GPUs Work������������������������������������������������������������������������������������������������384
GPU Building Blocks������������������������������������������������������������������������������������384
Simpler Processors (but More of Them)������������������������������������������������������386
Simplified Control Logic (SIMD Instructions)�����������������������������������������������391
Switching Work to Hide Latency������������������������������������������������������������������398
Offloading Kernels to GPUs�������������������������������������������������������������������������������400
SYCL Runtime Library����������������������������������������������������������������������������������400
GPU Software Drivers����������������������������������������������������������������������������������401

xii
Table of Contents

GPU Hardware���������������������������������������������������������������������������������������������402
Beware the Cost of Offloading!��������������������������������������������������������������������403
GPU Kernel Best Practices��������������������������������������������������������������������������������405
Accessing Global Memory���������������������������������������������������������������������������405
Accessing Work-Group Local Memory���������������������������������������������������������409
Avoiding Local Memory Entirely with Sub-Groups��������������������������������������412
Optimizing Computation Using Small Data Types����������������������������������������412
Optimizing Math Functions��������������������������������������������������������������������������413
Specialized Functions and Extensions��������������������������������������������������������414
Summary����������������������������������������������������������������������������������������������������������414
For More Information�����������������������������������������������������������������������������������415

Chapter 16: Programming for CPUs��������������������������������������������������417


Performance Caveats����������������������������������������������������������������������������������������418
The Basics of Multicore CPUs���������������������������������������������������������������������������419
The Basics of SIMD Hardware���������������������������������������������������������������������������422
Exploiting Thread-Level Parallelism������������������������������������������������������������������428
Thread Affinity Insight���������������������������������������������������������������������������������431
Be Mindful of First Touch to Memory�����������������������������������������������������������435
SIMD Vectorization on CPU��������������������������������������������������������������������������������436
Ensure SIMD Execution Legality������������������������������������������������������������������437
SIMD Masking and Cost������������������������������������������������������������������������������440
Avoid Array of Struct for SIMD Efficiency����������������������������������������������������442
Data Type Impact on SIMD Efficiency����������������������������������������������������������444
SIMD Execution Using single_task��������������������������������������������������������������446
Summary����������������������������������������������������������������������������������������������������������448

xiii
Table of Contents

Chapter 17: Programming for FPGAs������������������������������������������������451


Performance Caveats����������������������������������������������������������������������������������������452
How to Think About FPGAs��������������������������������������������������������������������������������452
Pipeline Parallelism�������������������������������������������������������������������������������������456
Kernels Consume Chip “Area”���������������������������������������������������������������������459
When to Use an FPGA����������������������������������������������������������������������������������������460
Lots and Lots of Work����������������������������������������������������������������������������������460
Custom Operations or Operation Widths������������������������������������������������������461
Scalar Data Flow�����������������������������������������������������������������������������������������462
Low Latency and Rich Connectivity�������������������������������������������������������������463
Customized Memory Systems���������������������������������������������������������������������464
Running on an FPGA�����������������������������������������������������������������������������������������465
Compile Times���������������������������������������������������������������������������������������������467
The FPGA Emulator��������������������������������������������������������������������������������������469
FPGA Hardware Compilation Occurs “Ahead-­of-Time”��������������������������������470
Writing Kernels for FPGAs���������������������������������������������������������������������������������471
Exposing Parallelism�����������������������������������������������������������������������������������472
Keeping the Pipeline Busy Using ND-Ranges����������������������������������������������475
Pipelines Do Not Mind Data Dependences!�������������������������������������������������478
Spatial Pipeline Implementation of a Loop��������������������������������������������������481
Loop Initiation Interval���������������������������������������������������������������������������������483
Pipes������������������������������������������������������������������������������������������������������������489
Custom Memory Systems����������������������������������������������������������������������������495
Some Closing Topics�����������������������������������������������������������������������������������������498
FPGA Building Blocks����������������������������������������������������������������������������������498
Clock Frequency������������������������������������������������������������������������������������������500
Summary����������������������������������������������������������������������������������������������������������501

xiv
Table of Contents

Chapter 18: Libraries������������������������������������������������������������������������503


Built-In Functions����������������������������������������������������������������������������������������������504
Use the sycl:: Prefix with Built-In Functions������������������������������������������������506
The C++ Standard Library��������������������������������������������������������������������������������507
oneAPI DPC++ Library (oneDPL)�����������������������������������������������������������������������510
SYCL Execution Policy���������������������������������������������������������������������������������511
Using oneDPL with Buffers��������������������������������������������������������������������������513
Using oneDPL with USM������������������������������������������������������������������������������517
Error Handling with SYCL Execution Policies����������������������������������������������519
Summary����������������������������������������������������������������������������������������������������������520

Chapter 19: Memory Model and Atomics�����������������������������������������523


What’s in a Memory Model?�����������������������������������������������������������������������������525
Data Races and Synchronization�����������������������������������������������������������������526
Barriers and Fences������������������������������������������������������������������������������������529
Atomic Operations���������������������������������������������������������������������������������������531
Memory Ordering�����������������������������������������������������������������������������������������532
The Memory Model�������������������������������������������������������������������������������������������534
The memory_order Enumeration Class�������������������������������������������������������536
The memory_scope Enumeration Class������������������������������������������������������538
Querying Device Capabilities�����������������������������������������������������������������������540
Barriers and Fences������������������������������������������������������������������������������������542
Atomic Operations in SYCL��������������������������������������������������������������������������543
Using Atomics with Buffers�������������������������������������������������������������������������548
Using Atomics with Unified Shared Memory�����������������������������������������������550
Using Atomics in Real Life��������������������������������������������������������������������������������550
Computing a Histogram�������������������������������������������������������������������������������551
Implementing Device-Wide Synchronization�����������������������������������������������553

xv
Table of Contents

Summary����������������������������������������������������������������������������������������������������������556
For More Information�����������������������������������������������������������������������������������557

Chapter 20: Backend Interoperability�����������������������������������������������559


What Is Backend Interoperability?��������������������������������������������������������������������559
When Is Backend Interoperability Useful?��������������������������������������������������������561
Adding SYCL to an Existing Codebase���������������������������������������������������������562
Using Existing Libraries with SYCL��������������������������������������������������������������564
Using Backend Interoperability for Kernels�������������������������������������������������������569
Interoperability with API-Defined Kernel Objects����������������������������������������569
Interoperability with Non-SYCL Source Languages�������������������������������������571
Backend Interoperability Hints and Tips�����������������������������������������������������������574
Choosing a Device for a Specific Backend��������������������������������������������������574
Be Careful About Contexts!��������������������������������������������������������������������������576
Access Low-Level API-Specific Features����������������������������������������������������576
Support for Other Backends������������������������������������������������������������������������577
Summary����������������������������������������������������������������������������������������������������������577

Chapter 21: Migrating CUDA Code����������������������������������������������������579


Design Differences Between CUDA and SYCL���������������������������������������������������579
Multiple Targets vs. Single Device Targets��������������������������������������������������579
Aligning to C++ vs. Extending C++�������������������������������������������������������������581
Terminology Differences Between CUDA and SYCL������������������������������������������582
Similarities and Differences������������������������������������������������������������������������������583
Execution Model������������������������������������������������������������������������������������������584
Memory Model��������������������������������������������������������������������������������������������589
Other Differences����������������������������������������������������������������������������������������592

xvi
Table of Contents

Features in CUDA That Aren’t In SYCL… Yet!����������������������������������������������������595


Global Variables�������������������������������������������������������������������������������������������595
Cooperative Groups�������������������������������������������������������������������������������������596
Matrix Multiplication Hardware�������������������������������������������������������������������597
Porting Tools and Techniques����������������������������������������������������������������������������598
Migrating Code with dpct and SYCLomatic�������������������������������������������������598
Summary����������������������������������������������������������������������������������������������������������603
For More Information����������������������������������������������������������������������������������������604

Epilogue: Future Direction of SYCL���������������������������������������������������605

Index�������������������������������������������������������������������������������������������������615

xvii
About the Authors
James Reinders is an Engineer at Intel Corporation with more than four
decades of experience in parallel computing and is an author/coauthor/
editor of more than ten technical books related to parallel programming.
James has a passion for system optimization and teaching. He has had the
great fortune to help make contributions to several of the world’s fastest
computers (#1 on the TOP500 list) as well as many other supercomputers
and software developer tools.

Ben Ashbaugh is a Software Architect at Intel Corporation where he has


worked for over 20 years developing software drivers and compilers for
Intel graphics products. For the past ten years, Ben has focused on parallel
programming models for general-purpose computation on graphics
processors, including SYCL and the DPC++ compiler. Ben is active in the
Khronos SYCL, OpenCL, and SPIR working groups, helping define industry
standards for parallel programming, and he has authored numerous
extensions to expose unique Intel GPU features.

James Brodman is a Principal Engineer at Intel Corporation working on


runtimes and compilers for parallel programming, and he is one of the
architects of DPC++. James has a Ph.D. in Computer Science from the
University of Illinois at Urbana-Champaign.

xix
About the Authors

Michael Kinsner is a Principal Engineer at Intel Corporation developing


parallel programming languages and compilers for a variety of
architectures. Michael contributes extensively to spatial architectures and
programming models and is an Intel representative within The Khronos
Group where he works on the SYCL and OpenCL industry standards for
parallel programming. Mike has a Ph.D. in Computer Engineering from
McMaster University and is passionate about programming models that
cross architectures while still enabling performance.

John Pennycook is a Software Enabling and Optimization Architect


at Intel Corporation, focused on enabling developers to fully utilize
the parallelism available in modern processors. John is experienced
in optimizing and parallelizing applications from a range of scientific
domains, and previously served as Intel’s representative on the steering
committee for the Intel eXtreme Performance User’s Group (IXPUG).
John has a Ph.D. in Computer Science from the University of Warwick.
His research interests are varied, but a recurring theme is the ability to
achieve application “performance portability” across different hardware
architectures.

Xinmin Tian is an Intel Fellow and Compiler Architect at Intel Corporation


and serves as Intel’s representative on the OpenMP Architecture Review
Board (ARB). Xinmin has been driving OpenMP offloading, vectorization,
and parallelization compiler technologies for Intel architectures. His
current focus is on LLVM-based OpenMP offloading, SYCL/DPC++
compiler optimizations for CPUs/GPUs, and tuning HPC/AI application
performance. He has a Ph.D. in Computer Science from Tsinghua
University, holds 27 US patents, has published over 60 technical papers
with over 1300+ citations of his work, and has coauthored two books that
span his expertise.

xx
Preface
If you are new to parallel programming that is okay. If you have never
heard of SYCL or the DPC++ compilerthat is also okay
Compared with programming in CUDA, C++ with SYCL offers
portability beyond NVIDIA, and portability beyond GPUs, plus a tight
alignment to enhance modern C++ as it evolves too. C++ with SYCL offers
these advantages without sacrificing performance.
C++ with SYCL allows us to accelerate our applications by harnessing
the combined capabilities of CPUs, GPUs, FPGAs, and processing devices
of the future without being tied to any one vendor.
SYCL is an industry-driven Khronos Group standard adding
advanced support for data parallelism with C++ to exploit accelerated
(heterogeneous) systems. SYCL provides mechanisms for C++ compilers
that are highly synergistic with C++ and C++ build systems. DPC++ is an
open source compiler project based on LLVM that adds SYCL support.
All examples in this book should work with any C++ compiler supporting
SYCL 2020 including the DPC++ compiler.
If you are a C programmer who is not well versed in C++, you are in
good company. Several of the authors of this book happily share that
they picked up much of C++ by reading books that utilized C++ like this
one. With a little patience, this book should also be approachable by C
programmers with a desire to write modern C++ programs.

Second Edition
With the benefit of feedback from a growing community of SYCL users, we
have been able to add content to help learn SYCL better than ever.

xxi
Preface

This edition teaches C++ with SYCL 2020. The first edition preceded
the SYCL 2020 specification, which differed only slightly from what the
first edition taught (the most obvious changes for SYCL 2020 in this edition
are the header file location, the device selector syntax, and dropping an
explicit host device).

Important resources for updated SYCL information, including any


known book errata, include the book GitHub (https://github.
com/Apress/data-parallel-CPP), the Khronos Group SYCL
standards website (www.khronos.org/sycl), and a key SYCL
education website (https://sycl.tech).

Chapters 20 and 21 are additions encouraged by readers of the first


edition of this book.
We added Chapter 20 to discuss backend interoperability. One of
the key goals of the SYCL 2020 standard is to enable broad support for
hardware from many vendors with many architectures. This required
expanding beyond the OpenCL-only backend support of SYCL 1.2.1. While
generally “it just works,” Chapter 20 explains this in more detail for those
who find it valuable to understand and interface at this level.
For experienced CUDA programmers, we have added Chapter 21 to
explicitly connect C++ with SYCL concepts to CUDA concepts both in
terms of approach and vocabulary. While the core issues of expressing
heterogeneous parallelism are fundamentally similar, C++ with SYCL offers
many benefits because of its multivendor and multiarchitecture approach.
Chapter 21 is the only place we mention CUDA terminology; the rest of this
book teaches using C++ and SYCL terminology with its open multivendor,
multiarchitecture approaches. In Chapter 21, we strongly encourage
looking at the open source tool “SYCLomatic” (github.com/oneapi-src/
SYCLomatic), which helps automate migration of CUDA code. Because it

xxii
Preface

is helpful, we recommend it as the preferred first step in migrating code.


Developers using C++ with SYCL have been reporting strong results on
NVIDIA, AMD, and Intel GPUs on both codes that have been ported from
CUDA and original C++ with SYCL code. The resulting C++ with SYCL
offers portability that is not possible with NVIDIA CUDA.
The evolution of C++, SYCL, and compilers including DPC++
continues. Prospects for the future are discussed in the Epilogue, after
we have taken a journey together to learn how to create programs for
heterogeneous systems using C++ with SYCL.
It is our hope that this book supports and helps grow the SYCL
community and helps promote data-parallel programming in C++
with SYCL.

Structure of This Book


This book takes us on a journey through what we need to know to be an
effective programmer of accelerated/heterogeneous systems using C++
with SYCL.

Chapters 1–4: Lay Foundations


Chapters 1–4 are important to read in order when first approaching C++
with SYCL.
Chapter 1 lays the first foundation by covering core concepts that are
either new or worth refreshing in our minds.
Chapters 2–4 lay a foundation of understanding for data-parallel
programming in C++ with SYCL. When we finish reading Chapters 1–4,
we will have a solid foundation for data-parallel programming in C++.
Chapters 1–4 build on each other and are best read in order.

xxiii
Preface

Chapters 5–12: Build on Foundations


With the foundations established, Chapters 5–12 fill in vital details by
building on each other to some degree while being easy to jump between
as desired. All these chapters should be valuable to all users of C++
with SYCL.

Chapters 13–21: Tips/Advice for SYCL in Practice


These final chapters offer advice and details for specific needs. We
encourage at least skimming them all to find content that is important to
your needs.

Epilogue: Speculating on the Future


The book concludes with an Epilogue that discusses likely and potential
future directions for C++ with SYCL, and the Data Parallel C++ compiler
for SYCL.
We wish you the best as you learn to use C++ with SYCL.

xxiv
Foreword
SYCL 2020 is a milestone in parallel computing. For the first time we have
a modern, stable, feature-complete, and portable open standard that can
target all types of hardware, and the book you hold in your hand is the
premier resource to learn SYCL 2020.
Computer hardware development is driven by our needs to solve
larger and more complex problems, but those hardware advances are
largely useless unless programmers like you and me have languages that
allow us to implement our ideas and exploit the power available with
reasonable effort. There are numerous examples of amazing hardware,
and the first solutions to use them have often been proprietary since it
saves time not having to bother with committees agreeing on standards.
However, in the history of computing, they have eventually always ended
up as vendor lock-in—unable to compete with open standards that allow
developers to target any hardware and share code—because ultimately the
resources of the worldwide community and ecosystem are far greater than
any individual vendor, not to mention how open software standards drive
hardware competition.
Over the last few years, my team has had the tremendous privilege
of contributing to shaping the emerging SYCL ecosystem through our
development of GROMACS, one of the world’s most widely used scientific
HPC codes. We need our code to run on every supercomputer in the
world as well as our laptops. While we cannot afford to lose performance,
we also depend on being part of a larger community where other teams
invest effort in libraries we depend on, where there are open compilers
available, and where we can recruit talent. Since the first edition of this
book, SYCL has matured into such a community; in addition to several

xxv
Foreword

vendor-provided compilers, we now have a major community-driven


implementation1 that targets all hardware, and there are thousands of
developers worldwide sharing experiences, contributing to training
events, and participating in forums. The outstanding power of open
source—whether it is an application, a compiler, or an open standard—is
that we can peek under the hood to learn, borrow, and extend. Just as we
repeatedly learn from the code in the Intel-led LLVM implementation,2
the community-driven implementation from Heidelberg University, and
several other codes, you can use our public repository3 to compare CUDA
and SYCL implementations in a large production codebase or borrow
solutions for your needs—because when you do so, you are helping to
further extend our community.
Perhaps surprisingly, data-parallel programming as a paradigm is
arguably far easier than classical solutions such as message-passing
communication or explicit multithreading—but it poses special challenges
to those of us who have spent decades in the old paradigms that focus on
hardware and explicit data placement. On a small scale, it was trivial for
us to explicitly decide how data is moved between a handful of processes,
but as the problem scales to thousands of units, it becomes a nightmare to
manage the complexity without introducing bugs or having the hardware
sit idle waiting for data. Data-parallel programming with SYCL solves
this by striking the balance of primarily asking us to explicitly express the
inherent parallelism of our algorithm, but once we have done that, the
compiler and drivers will mostly handle the data locality and scheduling
over tens of thousands of functional units. To be successful in data-parallel
programming, it is important not to think of a computer as a single unit
executing one program, but as a collection of units working independently

1
Community-driven implementation from Heidelberg University: tinyurl.com/
HeidelbergSYCL
2
DPC++ compiler project: github.com/intel/llvm
3
GROMACS: gitlab.com/gromacs/gromacs/

xxvi
Foreword

to solve parts of a large problem. As long as we can express our problem as


an algorithm where each part does not have dependencies on other parts,
it is in theory straightforward to implement it, for example, as a parallel
for-loop that is executed on a GPU through a device queue. However, for
more practical examples, our problems are frequently not large enough
to use an entire device efficiently, or we depend on performing tens of
thousands of iterations per second where latency in device drivers starts
to be a major bottleneck. While this book is an outstanding introduction to
performance-portable GPU programming, it goes far beyond this to show
how both throughput and latency matter for real-world applications, how
SYCL can be used to exploit unique features both of CPUs, GPUs, SIMD
units, and FPGAs, but it also covers the caveats that for good performance
we need to understand and possibly adapt code to the characteristics of
each type of hardware. Doing so, it is not merely a great tutorial on data-
parallel programing, but an authoritative text that anybody interested in
programming modern computer hardware in general should read.
One of SYCL’s key strengths is the close alignment to modern C++.
This can seem daunting at first; C++ is not an easy language to fully master
(I certainly have not), but Reinders and coauthors take our hand and lead
us on a path where we only need to learn a handful of C++ concepts to get
started and be productive in actual data-parallel programming. However,
as we become more experienced, SYCL 2020 allows us to combine this
with the extreme generality of C++17 to write code that can be dynamically
targeted to different devices, or relying on heterogeneous parallelism that
uses CPU, GPU, and network units in parallel for different tasks. SYCL is
not a separate bolted-on solution to enable accelerators but instead holds
great promise to be the general way we express data parallelism in C++.
The SYCL 2020 standard now includes several features previously only
available as vendor extensions, for example, Unified Shared Memory,
sub-groups, atomic operations, reductions, simpler accessors, and many
other concepts that make code cleaner, and facilitates both development
as well as porting from standard C++17 or CUDA to have your code target

xxvii
Foreword

more diverse hardware. This book provides a wonderful and accessible


introduction to all of them, and you will also learn how SYCL is expected to
evolve together with the rapid development C++ is undergoing.
This all sounds great in theory, but how portable is SYCL in practice?
Our application is an example of a codebase that is quite challenging to
optimize since data access patterns are random, the amount of data to
process in each step is limited, we need to achieve thousands of iterations
per second, and we are limited both by memory bandwidth, floating-point,
and integer operations—it is an extreme opposite of a simple data-parallel
problem. We spent over two decades writing assembly SIMD instructions
and native implementations for several GPU architectures, and our
very first encounters with SYCL involved both pains with adapting to
differences and reporting performance regressions to driver and compiler
developers. However, as of spring 2023, our SYCL kernels can achieve
80–100% of native performance on all GPU architectures not only from a
single codebase but even a single precompiled binary.
SYCL is still young and has a rapidly evolving ecosystem. There are
a few things not yet part of the language, but SYCL is unique as the only
performance-portable standard available that successfully targets all
modern hardware. Whether you are a beginner wanting to learn parallel
programming, an experienced developer interested in data-parallel
programming, or a maintainer needing to port 100,000 lines of proprietary
API code to an open standard, this second edition is the only book you will
need to become part of this community.

Erik Lindahl
Professor of Biophysics
Dept. Biophysics & Biochemistry
Science for Life Laboratory
Stockholm University

xxviii
Acknowledgments
We have been blessed with an outpouring of community input for this
second edition of our book. Much inspiration came from interactions with
developers as they use SYCL in production, classes, tutorials, workshops,
conferences, and hackathons. SYCL deployments that include NVIDIA
hardware, in particular, have helped us enhance the inclusiveness and
practical tips in our teaching of SYCL in this second edition.
The SYCL community has grown a great deal—and consists of
engineers implementing compilers and tools, and a much larger group of
users that adopt SYCL to target hardware of many types and vendors. We
are grateful for their hard work, and shared insights.
We thank the Khronos SYCL Working Group that has worked diligently
to produce a highly functional specification. In particular, Ronan Keryell
has been the SYCL specification editor and a longtime vocal advocate
for SYCL.
We are in debt to the numerous people who gave us feedback from
the SYCL community in all these ways. We are also deeply grateful for
those who helped with the first edition a few years ago, many of whom we
named in the acknowledgement of that edition.
The first edition received feedback via GitHub,1 which we did review
but we were not always prompt in acknowledging (imagine six coauthors
all thinking “you did that, right?”). We did benefit a great deal from that
feedback, and we believe we have addressed all the feedback in the
samples and text for this edition. Jay Norwood was the most prolific at
commenting and helping us—a big thank you to Jay from all the authors!

1
github.com/apress/data-parallel-CPP

xxix
Acknowledgments

Other feedback contributors include Oscar Barenys, Marcel Breyer, Jeff


Donner, Laurence Field, Michael Firth, Piotr Fusik, Vincent Mierlak, and
Jason Mooneyham. Regardless of whether we recalled your name here or
not, we thank everyone who has provided feedback and helped refine our
teaching of C++ with SYCL.
For this edition, a handful of volunteers tirelessly read draft
manuscripts and provided insightful feedback for which we are incredibly
grateful. These reviewers include Aharon Abramson, Thomas Applencourt,
Rod Burns, Joe Curley, Jessica Davies, Henry Gabb, Zheming Jin, Rakshith
Krishnappa, Praveen Kundurthy, Tim Lewis, Eric Lindahl, Gregory Lueck,
Tony Mongkolsmai, Ruyman Reyes Castro, Andrew Richards, Sanjiv Shah,
Neil Trevett, and Georg Viehöver.
We all enjoy the support of our family and friends, and we cannot
thank them enough. As coauthors, we have enjoyed working as a team
challenging each other and learning together along the way. We appreciate
our collaboration with the entire Apress team in getting this book
published.
We are sure that there are more than a few people whom we have failed
to mention explicitly who have positively impacted this book project. We
thank all who helped us.
As you read this second edition, please do provide feedback if you find
any way to improve it. Feedback via GitHub can open up a conversation,
and we will update the online errata and book samples as needed.
Thank you all, and we hope you find this book invaluable in your
endeavors.

xxx
CHAPTER 1

Introduction
We have undeniably entered the age of accelerated computing. In order to
satisfy the world’s insatiable appetite for more computation, accelerated
computing drives complex simulations, AI, and much more by providing
greater performance and improved power efficiency when compared with
earlier solutions.
Heralded as a “New Golden Age for Computer Architecture,”1 we are
faced with enormous opportunity through a rich diversity in compute
devices. We need portable software development capabilities that are
not tied to any single vendor or architecture in order to realize the full
potential for accelerated computing.
SYCL (pronounced sickle) is an industry-driven Khronos Group
standard adding advanced support for data parallelism with C++ to
support accelerated (heterogeneous) systems. SYCL provides mechanisms
for C++ compilers to exploit accelerated (heterogeneous) systems in a way
that is highly synergistic with modern C++ and C++ build systems. SYCL is
not an acronym; SYCL is simply a name.

1
A New Golden Age for Computer Architecture by John L. Hennessy, David
A. Patterson; Communications of the ACM, February 2019, Vol. 62 No. 2,
Pages 48-60.

© Intel Corporation 2023 1


J. Reinders et al., Data Parallel C++, https://doi.org/10.1007/978-1-4842-9691-2_1
Chapter 1 Introduction

ACCELERATED VS. HETEROGENEOUS

These terms go together. Heterogeneous is a technical description


acknowledging the combination of compute devices that are programmed
differently. Accelerated is the motivation for adding this complexity to systems
and programming. There is no guarantee of acceleration ever; programming
heterogeneous systems will only accelerate our applications when we do it
right. This book helps teach us how to do it right!

Data parallelism in C++ with SYCL provides access to all the compute
devices in a modern accelerated (heterogeneous) system. A single C++
application can use any combination of devices—including GPUs, CPUs,
FPGAs, and application-specific integrated circuits (ASICs)—that are
suitable to the problems at hand. No proprietary, single-vendor, solution
can offer us the same level of flexibility.
This book teaches us how to harness accelerated computing using
data-parallel programming using C++ with SYCL and provides practical
advice for balancing application performance, portability across compute
devices, and our own productivity as programmers. This chapter lays
the foundation by covering core concepts, including terminology, which
are critical to have fresh in our minds as we learn how to accelerate C++
programs using data parallelism.

Read the Book, Not the Spec


No one wants to be told “Go read the spec!”—specifications are hard to
read, and the SYCL specification (www.khronos.org/sycl/) is no different.
Like every great language specification, it is full of precision but is light on
motivation, usage, and teaching. This book is a “study guide” to teach C++
with SYCL.

2
Chapter 1 Introduction

No book can explain everything at once. Therefore, this chapter does


what no other chapter will do: the code examples contain programming
constructs that go unexplained until future chapters. We should not get
hung up on understanding the coding examples completely in Chapter 1
and trust it will get better with each chapter.

SYCL 2020 and DPC++


This book teaches C++ with SYCL 2020. The first edition of this book
preceded the SYCL 2020 specification, so this edition includes updates
including adjustments in the header file location (sycl instead of CL),
device selector syntax, and removal of an explicit host device.
DPC++ is an open source compiler project based on LLVM. It is
our hope that SYCL eventually be supported by default in the LLVM
community and that the DPC++ project will help make that happen. The
DPC++ compiler offers broad heterogeneous support that includes GPU,
CPU, and FPGA. All examples in this book work with the DPC++ compiler
and should work with any C++ compiler supporting SYCL 2020.

Important resources for updated SYCL information, including any


known book errata, include the book GitHub (github.com/Apress/
data-parallel-CPP), the Khronos Group SYCL standards website
(www.khronos.org/sycl), and a key SYCL education website
(sycl.tech).

As of publication time, no C++ compiler claims full conformance or


compliance with the SYCL 2020 specification. Nevertheless, the code in
this book works with the DPC++ compiler and should work with other C++
compilers that have most of SYCL 2020 implemented. We use only standard
C++ with SYCL 2020 excepting for a few DPC++-specific extensions that

3
Chapter 1 Introduction

are clearly called out in Chapter 17 (Programming for FPGAs) to a small


degree, Chapter 20 (Backend Interoperability) when connecting to Level
Zero backends, and the Epilogue when speculating on the future.

Why Not CUDA?


Unlike CUDA, SYCL supports data parallelism in C++ for all vendors and
all types of architectures (not just GPUs). CUDA is focused on NVIDIA
GPU support only, and efforts (such as HIP/ROCm) to reuse it for GPUs
by other vendors have limited ability to succeed despite some solid
success and usefulness. With the explosion of accelerator architectures,
only SYCL offers the support we need for harnessing this diversity and
offering a multivendor/multiarchitecture approach to help with portability
that CUDA does not offer. To more deeply understand this motivation,
we highly recommend reading (or watching the video recording of their
excellent talk) “A New Golden Age for Computer Architecture” by industry
legends John L. Hennessy and David A. Patterson. We consider this a
must-read article.
Chapter 21, in addition to addressing topics useful for migrating code
from CUDA to C++ with SYCL, is valuable for those experienced with
CUDA to bridge some terminology and capability differences. The most
significant capabilities beyond CUDA come from the ability for SYCL to
support multiple vendors, multiple architectures (not just GPUs), and
multiple backends even for the same device. This flexibility answers the
question “Why not CUDA?”
SYCL does not involve any extra overhead compared with CUDA or
HIP. It is not a compatibility layer—it is a generalized approach that is open
to all devices regardless of vendor and architecture while simultaneously
being in sync with modern C++. Like other open multivendor and
multiarchitecture techniques, such as OpenMP and OpenCL, the ultimate
proof is in the implementations including options to access hardware-­
specific optimizations when absolutely needed.

4
Chapter 1 Introduction

Why Standard C++ with SYCL?


As we will point out repeatedly, every program using SYCL is first and
foremost a C++ program. SYCL does not rely on any language changes
to C++. SYCL does take C++ programming places it cannot go without
SYCL. We have no doubt that all programming for accelerated computing
will continue to influence language standards including C++, but we do
not believe the C++ standard should (or will) evolve to displace the need
for SYCL any time soon. SYCL has a rich set of capabilities that we spend
this book covering that extend C++ through classes and rich support for
new compiler capabilities necessary to meet needs (already existing today)
for multivendor and multiarchitecture support.

Getting a C++ Compiler with SYCL Support


All examples in this book compile and work with all the various
distributions of the DPC++ compiler and should compile with other C++
compilers supporting SYCL (see “SYCL Compilers in Development” at
www.khronos.org/sycl). We are careful to note the very few places where
extensions are used that are DPC++ specific at the time of publication.
The authors recommend the DPC++ compiler for a variety of reasons,
including our close association with the DPC++ compiler. DPC++ is an
open source compiler project to support SYCL. By using LLVM, the DPC++
compiler project has access to backends for numerous devices. This has
already resulted in support for Intel, NVIDIA, and AMD GPUs, numerous
CPUs, and Intel FPGAs. The ability to extend and enhance support openly
for multiple vendors and multiple architecture makes LLVM a great choice
for open source efforts to support SYCL.
There are distributions of the DPC++ compiler, augmented with
additional tools and libraries, available as part of a larger project to
offer broad support for heterogeneous systems, which include libraries,

5
Chapter 1 Introduction

debuggers, and other tools, known as the oneAPI project. The oneAPI
tools, including the DPC++ compiler, are freely available (www.oneapi.io/
implementations).

1. #include <iostream>
2. #include <sycl/sycl.hpp>
3. using namespace sycl;
4.
5. const std::string secret{
6. "Ifmmp-!xpsme\"\012J(n!tpssz-!Ebwf/!"
7. "J(n!bgsbje!J!dbo(u!ep!uibu/!.!IBM\01"};
8.
9. const auto sz = secret.size();
10.
11. int main() {
12. queue q;
13.
14. char* result = malloc_shared<char>(sz, q);
15. std::memcpy(result, secret.data(), sz);
16.
17. q.parallel_for(sz, [=](auto& i) {
18. result[i] -= 1;
19. }).wait();
20.
21. std::cout << result << "\n";
22. free(result, q);
23. return 0;
24. }

Figure 1-1. Hello data-parallel programming

 ello, World! and a SYCL


H
Program Dissection
Figure 1-1 shows a sample SYCL program. Compiling and running it
results in the following being printed:
Hello, world! (and some additional text left to experience by running it)
We will completely understand this example by the end of Chapter 4.
Until then, we can observe the single include of <sycl/sycl.hpp> (line 2)
that is needed to define all the SYCL constructs. All SYCL constructs live
inside a namespace called sycl.

6
Chapter 1 Introduction

• Line 3 lets us avoid writing sycl:: over and over.

• Line 12 instantiates a queue for work requests directed


to a particular device (Chapter 2).

• Line 14 creates an allocation for data shared with the


device (Chapter 3).

• Line 15 copies the secret string into device memory,


where it will be processed by the kernel.

• Line 17 enqueues work to the device (Chapter 4).

• Line 18 is the only line of code that will run on the


device. All other code runs on the host (CPU).

Line 18 is the kernel code that we want to run on devices. That kernel
code decrements a single character. With the power of parallel_for(),
that kernel is run on each character in our secret string in order to decode
it into the result string. There is no ordering of the work required, and it is
run asynchronously relative to the main program once the parallel_for
queues the work. It is critical that there is a wait (line 19) before looking at
the result to be sure that the kernel has completed, since in this example
we are using a convenient feature (Unified Shared Memory, Chapter 6).
Without the wait, the output may occur before all the characters have been
decrypted. There is more to discuss, but that is the job of later chapters.

Queues and Actions


Chapter 2 discusses queues and actions, but we can start with a simple
explanation for now. Queues are the only connection that allows an
application to direct work to be done on a device. There are two types
of actions that can be placed into a queue: (a) code to execute and (b)
memory operations. Code to execute is expressed via either single_task
or parallel_for (used in Figure 1-1). Memory operations perform copy

7
Chapter 1 Introduction

operations between host and device or fill operations to initialize memory.


We only need to use memory operations if we seek more control than
what is done automatically for us. These are all discussed later in the
book starting with Chapter 2. For now, we should be aware that queues
are the connection that allows us to command a device, and we have
a set of actions available to put in queues to execute code and to move
around data. It is also very important to understand that requested actions
are placed into a queue without waiting. The host, after submitting an
action into a queue, continues to execute the program, while the device
will eventually, and asynchronously, perform the action requested via
the queue.

QUEUES CONNECT US TO DEVICES

We submit actions into queues to request computational work and data


movement.

Actions happen asynchronously.

It Is All About Parallelism


Since programming in C++ for data parallelism is all about parallelism,
let’s start with this critical concept. The goal of parallel programming is
to compute something faster. It turns out there are two aspects to this:
increased throughput and reduced latency.

Throughput
Increasing throughput of a program comes when we get more work done
in a set amount of time. Techniques like pipelining may stretch out the
time necessary to get a single work-item done, to allow overlapping of

8
Chapter 1 Introduction

work that leads to more work-per-unit-of-time being done. Humans


encounter this often when working together. The very act of sharing work
involves overhead to coordinate that often slows the time to do a single
item. However, the power of multiple people leads to more throughput.
Computers are no different—spreading work to more processing cores
adds overhead to each unit of work that likely results in some delays, but
the goal is to get more total work done because we have more processing
cores working together.

Latency
What if we want to get one thing done faster—for instance, analyzing
a voice command and formulating a response? If we only cared about
throughput, the response time might grow to be unbearable. The concept
of latency reduction requires that we break up an item of work into
pieces that can be tackled in parallel. For throughput, image processing
might assign whole images to different processing units—in this case,
our goal may be optimizing for images per second. For latency, image
processing might assign each pixel within an image to different processing
cores—in this case, our goal may be maximizing pixels per second from a
single image.

Think Parallel
Successful parallel programmers use both techniques in their
programming. This is the beginning of our quest to Think Parallel.
We want to adjust our minds to think first about where parallelism
can be found in our algorithms and applications. We also think about how
different ways of expressing the parallelism affect the performance we
ultimately achieve. That is a lot to take in all at once. The quest to Think
Parallel becomes a lifelong journey for parallel programmers. We can learn
a few tips here.

9
Chapter 1 Introduction

Amdahl and Gustafson


Amdahl’s Law, stated by the supercomputer pioneer Gene Amdahl in
1967, is a formula to predict the theoretical maximum speed-up when
using multiple processors. Amdahl lamented that the maximum gain from
parallelism is limited to (1/(1-p)) where p is the fraction of the program
that runs in parallel. If we only run two-thirds of our program in parallel,
then the most that program can speed up is a factor of 3. We definitely
need that concept to sink in deeply! This happens because no matter how
fast we make that two-thirds of our program run, the other one-third still
takes the same time to complete. Even if we add 100 GPUs, we will only get
a factor of 3 increase in performance.
For many years, some viewed this as proof that parallel computing
would not prove fruitful. In 1988, John Gustafson wrote an article titled
“Reevaluating Amdahl’s Law.” He observed that parallelism was not used
to speed up fixed workloads, but it was used to allow work to be scaled
up. Humans experience the same thing. One delivery person cannot
deliver a single package faster with the help of many more people and
trucks. However, a hundred people and trucks can deliver one hundred
packages more quickly than a single driver with a truck. Multiple drivers
will definitely increase throughput and will also generally reduce latency
for package deliveries. Amdahl’s Law tells us that a single driver cannot
deliver one package faster by adding ninety-nine more drivers with their
own trucks. Gustafson noticed the opportunity to deliver one hundred
packages faster with these extra drivers and trucks.
This emphasizes that parallelism is most useful because the size of
problems we tackle keep growing in size year after year. Parallelism would
not nearly as important to study if year after year we only wanted to run the
same size problems faster. This quest to solve larger and larger problems
fuels our interest in exploiting data parallelism, using C++ with SYCL, for
the future of computer (heterogeneous/accelerated systems).

10
Random documents with unrelated
content Scribd suggests to you:
indien de aantrekkelijkste en tegelijkertijd krachtigste mannen aan
de aantrekkelijkste vrouwen, en ook deze laatsten aan de eersten de
voorkeur gaven. En deze twee vormen van teeltkeus schijnen
werkelijk beide, hetzij al dan niet gelijktijdig, bij het menschelijk
geslacht plaats te hebben gegrepen, vooral gedurende de vroege
tijdvakken van onze lange geschiedenis.

Wij zullen nu een weinig uitvoeriger, in verband met de seksueele


teeltkeus, eenige van de kenmerken beschouwen, die de
verschillende menschenrassen van elkander en van de lagere dieren
onderscheiden, namelijk het meer of min volkomen ontbreken van
haar op het lichaam en de kleur der huid. Wij behoeven niets te
zeggen over de groote verscheidenheid in den vorm der
gelaatstrekken en van den schedel bij de verschillende rassen, daar
wij in het laatste hoofdstuk hebben gezien, hoe verschillend de
maatstaf der schoonheid in deze opzichten is. Op deze kenmerken
zal daarom waarschijnlijk de seksueele teeltkeus hebben ingewerkt;
maar, zoover ik kan nagaan, hebben wij geen [368]middel om te
beoordeelen, of er voornamelijk van de mannelijke dan wel van de
vrouwelijke zijde is ingewerkt. De muzikale vermogens van den
mensch zijn eveneens besproken.

Het Ontbreken van Haar op het Lichaam en de Ontwikkeling daarvan


op het Aangezicht en het Hoofd.—Uit de aanwezigheid van het
wolachtige haar of lanugo op den menschelijken foetus en van op
volwassen leeftijd hier en daar over het lichaam verspreide
rudimentaire haren, mogen wij afleiden, dat de mensch van het
eene of andere dier afstamt, dat behaard werd geboren en zulks
levenslang bleef. Het verlies van het haar is hinderlijk en
waarschijnlijk nadeelig voor den mensch zelfs in een warm klimaat;
want hij wordt daar blootgesteld aan plotselinge afkoelingen, vooral
gedurende vochtig weder. Gelijk de heer Wallace opmerkt, zijn in alle
landen de inboorlingen blijde hun naakte ruggen en schouders door
de eene of andere dunne bekleeding te kunnen beschermen.
Niemand veronderstelt, dat de naaktheid der huid den mensch eenig
direct voordeel aanbrengt, zoodat hij zijn haarkleed niet kan hebben
verloren door natuurlijke teeltkeus. 23 Wij hebben ook volstrekt geen
gronden om aan te nemen, gelijk in een vorig hoofdstuk is
aangetoond, dat dit verlies het gevolg kan zijn van de directe
werking der levensvoorwaarden waaraan de mensch lang is
blootgesteld geweest, of dat het het gevolg van correlatieve
ontwikkeling is.

Het ontbreken van haar op het lichaam is tot op zekere hoogte een
[369]secundair seksueel kenmerk; want in alle deelen der wereld zijn
de vrouwen minder harig dan de mannen. Wij mogen derhalve op
redelijke gronden vermoeden, dat het een kenmerk is, hetwelk door
seksueele teeltkeus werd verkregen. Wij weten, dat de aangezichten
van onderscheidene soorten van apen, en groote plekken aan het
achtereinde van het lichaam bij andere soorten, van haar zijn
ontbloot; en wij mogen dit veilig toeschrijven aan seksueele
teeltkeus; want deze plekken zijn niet alleen levendig gekleurd, maar
somtijds, gelijk bij den mannelijken Mandril en den vrouwelijken
Rhesus-aap, veel levendiger bij de eene sekse dan bij de andere.
(4) Naarmate deze dieren allengs tot vollen wasdom komen, nemen
de naakte plekken, naar de heer Bartlett mij heeft medegedeeld, in
omvang toe naar verhouding tot de geheele grootte hunner
lichamen. Het haar schijnt echter in deze gevallen niet te zijn
verwijderd ter wille van de naaktheid, maar opdat de kleur der huid
meer volkomen zou kunnen worden ten toon gesteld. Evenzoo zijn
ook bij vele vogels de kop en de hals door seksueele teeltkeus van
vederen ontbloot, opdat de levendig gekleurde huid zou kunnen
worden ten toon gesteld.

Daar de vrouw een minder harig lichaam heeft dan de man, en daar
dit kenmerk aan alle rassen gemeen is, mogen wij besluiten, dat
waarschijnlijk eerst onze vrouwelijke half-menschelijke voorouders
hun haar gedeeltelijk verloren, en dat dit plaats greep in een uiterst
verwijderd tijdvak, vóór de onderscheidene menschenrassen zich uit
een gemeenschappelijken stam in verschillende richtingen hadden
ontwikkeld. Toen onze vrouwelijke voorouders dit nieuwe kenmerk
van naaktheid allengs verkregen, moeten zij het in bijna gelijke mate
op hun jong kroost van beiderlei sekse hebben overgeplant, zoodat
de overplanting daarvan, gelijk met vele versierselen bij zoogdieren
en vogels het geval is, niet is beperkt noch door den leeftijd noch
door de sekse. Er is niets verwonderlijks in, dat een gedeeltelijk
verlies van het haar door de op apen gelijkende voorouders van den
mensch als een sieraad is beschouwd; want wij hebben gezien, dat
bij dieren van allerlei soort tallooze vreemde kenmerken als zoodanig
zijn beschouwd en bijgevolg door seksueele teeltkeus gewijzigd. Het
is ook niet te verwonderen, dat daardoor een eenigermate nadeelig
kenmerk zou zijn verkregen; want wij weten, dat dit het geval is met
de siervederen van sommige vogels en met de horens van sommige
herten.

De wijfjes van sommige anthropoïde apen zijn, gelijk in een vorig


hoofdstuk [370]werd vermeld, een weinig minder harig op de
ondervlakte van het lichaam dan de mannetjes, en hier hebben wij
een punt dat tot uitgang voor het proces der ontharing kan hebben
gediend. Ten opzichte van de voleindiging van dit proces door
seksueele teeltkeus is het goed ons het Nieuw-Zeelandsch
spreekwoord te herinneren: „Er is geen vrouw voor een harig man.”
Allen die photographieën van de Siameesche harige familie hebben
gezien, zullen toegeven, dat het tegenovergestelde uiterste van
buitengewoon sterke behaardheid, belachelijk en afgrijselijk leelijk is.
De koning van Siam moest zelfs een man door het geven van geld er
toe brengen om de eerste harige vrouw in de familie te huwen, die
dit kenmerk op haar jong kroost van beiderlei sekse overplantte. 24
Sommige rassen zijn veel hariger dan andere, vooral aan den
mannelijken kant; maar men mag niet aannemen, dat de harigste
rassen, bij voorbeeld Europeanen, volkomener een oorspronkelijken
toestand hebben behouden dan de naakte rassen, zooals de
Kalmukken of de Amerikanen. Het is een waarschijnlijker meening,
dat de harigheid der eersten het gevolg is van een gedeeltelijk
atavisme; want kenmerken die lang zijn overgeërfd, zijn altijd
geneigd om terug te keeren. Een merkwaardig geval is door Pinel
opgeteekend van een idioot, gezonken tot het peil van een redeloos
dier, wiens rug, lendenen en schouders waren bedekt met haar, van
2,5 tot 5 centimeter lang. Nog eenige dergelijke gevallen zijn
bekend. Het schijnt niet, dat een koud klimaat van invloed is
geweest op en heeft geleid tot deze soort van atavisme; behalve
wellicht bij de negers die gedurende verscheidene generatiën in de
Vereenigde Staten zijn opgegroeid 25, en wellicht [371]bij de Aino’s die
de noordelijke eilanden van den Japanschen archipel bewonen. De
wetten der erfelijkheid zijn echter zoo ingewikkeld, dat wij zelden
haar werking begrijpen. Indien de grootere harigheid van sommige
rassen het gevolg van atavisme was, zonder dat daarop eenige vorm
van teeltkeus een belemmerenden invloed uitoefende, dan zou de
uitermate groote veranderlijkheid (variabiliteit) van dit kenmerk,
zelfs binnen de grenzen van het zelfde ras, ophouden opmerkelijk te
zijn.

Wat den baard aangaat, zoo vinden wij, als wij ons tot onze beste
gidsen, namelijk de Vierhandige Zoogdieren (Quadrumana), wenden,
baarden bij vele soorten bij beide seksen even goed ontwikkeld,
maar bij andere, hetzij tot de mannetjes beperkt of bij deze meer
ontwikkeld dan bij de wijfjes. Wegens dit feit en wegens de
merkwaardige rangschikking en ook levendige kleuren van het haar
op de koppen van vele apen, is het hoogst waarschijnlijk, gelijk
vroeger is uitgelegd, dat de mannetjes eerst hun baarden als een
versiering door seksueele teeltkeus verkregen, en ze in de meeste
gevallen in gelijke of bijna gelijke mate op hun nakomelingen van
beiderlei seksen overplantten. Wij weten door Eschricht 26, dat bij
den mensch de vrouwelijke foetus even goed als de mannelijke veel
haar op het aangezicht, vooral rondom den mond bezit; en dit toont
aan, dat wij afstammen van voorouders bij welke beide seksen
gebaard waren. Het schijnt daarom op het eerste gezicht
waarschijnlijk, dat de man zijn baard uit een zeer vroeg tijdperk
heeft behouden, terwijl de vrouw haar baard verloor op den zelfden
tijd, toen haar lichaam bijna volkomen van haren werd ontbloot.
Zelfs de kleur van den baard bij den mensch schijnt te zijn
overgeërfd van een op een aap gelijkenden voorvader; want, als er
eenig verschil in tint is tusschen het hoofdhaar en het baardhaar,
dan is dit laatste bij alle apen en bij den mensch lichter gekleurd. Er
is minder onwaarschijnlijkheid in gelegen, dat de mannen van de
gebaarde rassen hun baarden uit oorspronkelijke tijden hebben
behouden, dan in het geval van het haar op het lichaam; want bij de
Vierhandigen (Quadrumana) bij welke het mannetje een grooteren
baard dan het wijfje heeft, komt deze slechts op volwassen leeftijd
tot volkomen ontwikkeling, en de latere ontwikkelingstrappen
kunnen wellicht uitsluitend op den mensch zijn overgeplant. Wij
kunnen dan begrijpen, waarom onze mannelijke kinderen, gelijk
werkelijk het geval is, voordat zij den volwassen leeftijd
[372]bereiken, even ontbloot van baarden zijn als onze vrouwelijke
kinderen. Daarentegen bewijst de groote veranderlijkheid
(variabiliteit) van den baard binnen de grenzen van het zelfde ras en
bij verschillende rassen, dat er atavisme in het spel moet zijn
gekomen. Hoe dit ook moge zijn, wij moeten de rol die de seksueele
teeltkeus zelfs gedurende latere tijden kan hebben gespeeld, niet
voorbijzien; want wij weten, dat bij wilden de mannen van de
baardelooze rassen zich oneindig veel moeite geven om elk haartje
op hun gelaat uit te trekken, als iets onaangenaams, terwijl de
mannen van de gebaarde rassen den grootsten hoogmoed over hun
baarden gevoelen. De vrouwen deelen ongetwijfeld in deze
gevoelens, en indien dit zoo is, kan het moeilijk missen, of de
seksueele teeltkeus heeft in den loop van latere tijden eenigen
invloed uitgeoefend. 27 Het is ook mogelijk, dat de lang voortgezette
gewoonte om het haar uit te trekken, erfelijke gevolgen heeft gehad.
Dr. Brown-Séquard heeft aangetoond, dat, wanneer sommige dieren
op een bijzondere wijze worden geopereerd, hun jongen daardoor
worden aangedaan. Nog meer bewijzen zouden kunnen worden
geleverd van de erfelijkheid van de gevolgen van verminkingen;
maar een voor eenige jaren door den heer Salvin 28 ontdekt feit staat
meer rechtstreeks in betrekking tot de onderhavige vraag; want hij
heeft aangetoond, dat bij de motmots van welke men weet, dat zij
gewoon zijn de vlag van de beide middelste staartvederen af te
bijten, de vlag van deze vederen van nature eenigszins kleiner is. 29
Desniettemin zou bij den mensch de gewoonte van den baard en de
haren op het lichaam uit te trekken, [373]waarschijnlijk niet zijn
ontstaan, wanneer die niet reeds om de eene of andere reden
minder waren geworden.

Het is vrij moeilijk zich er een oordeel over te vormen, hoe het lange
haar op onze hoofden tot ontwikkeling kwam. Eschricht 30 getuigt,
dat bij den menschelijken foetus het haar op het gelaat gedurende
de vijfde maand langer is dan op het hoofd; en dit bewijst, dat onze
half-menschelijke voorouders niet van lange lokken waren voorzien,
die derhalve een laat verworven kenmerk moeten zijn geweest. Dit
wordt eveneens aangetoond door het buitengewone verschil in de
lengte van het haar bij de verschillende rassen; bij den Neger vormt
het haar eenvoudig een gekroesde mat; bij ons is het zeer lang, en
bij de inboorlingen van Amerika reikt het niet zelden tot op den
grond. Bij sommige soorten van Slankapen (Semnopithecus) is de
kop met matig lang haar bedekt, en dit dient waarschijnlijk tot
versiering en werd door seksueele teeltkeus verkregen. De zelfde
meening mag tot den mensch worden uitgebreid; want wij weten,
dat lange lokken heden ten dage zeer worden bewonderd, en zulks
ook vroeger werden; de apostel Paulus zegt: „Soo een vrouwe langh
hair draeght, dat het haer een eere is” (6); en wij hebben gezien,
dat in Noord-Amerika iemand alleen wegens de lengte van zijn haar
tot opperhoofd werd gekozen.

Kleur der Huid.—De beste soort van bewijs, dat de kleur der huid
door seksueele teeltkeus is gewijzigd, ontbreekt in het geval van den
mensch; want de seksen verschillen in dit opzicht in het geheel niet,
of slechts weinig en twijfelachtig. (7) Van den anderen kant weten
wij uit vele reeds medegedeelde feiten, dat de kleur der huid door
menschen van alle rassen als een hoogst belangrijk element van hun
schoonheid wordt beschouwd, zoodat het een kenmerk is, dat
geschikt zou zijn om door teeltkeus te worden gewijzigd, gelijk in
tallooze voorbeelden bij de lagere dieren is geschied. Het schijnt op
het eerste gezicht een monsterachtige veronderstelling, dat de
gitzwartheid van den neger door seksueele teeltkeus is verkregen;
doch deze meening wordt door onderscheidene analogieën
ondersteund, en wij weten, dat [374]de negers hun eigen zwartheid
bewonderen. Als bij Zoogdieren de seksen in kleur verschillen, is het
mannetje dikwijls zwart of veel donkerder dan het wijfje; en het
hangt eenvoudig van den vorm van erfelijkheid af, of deze of eenige
andere tint op beide seksen of alleen op ééne sekse zal worden
overgeplant. De gelijkenis van den Joden- of Satans-aap (Pithecia
satanas) met zijn gitzwarte huid, witte rollende oogappels en zijn op
de kruin van het hoofd gescheiden haar op een neger in miniatuur is
bijna belachelijk.

De kleur van het gelaat verschilt bij de onderscheidene soorten van


apen veel meer dan bij de menschenrassen; en wij hebben goede
reden om te gelooven, dat de roode, blauwe, oranje, bijna witte en
zwarte kleuren van hun huid, zelfs wanneer zij aan beide seksen
gemeen zijn, en de levendige kleuren van hun pels, zoowel als tot de
versiering strekkende haarbossen aan den kop, allen door seksueele
teeltkeus zijn verkregen. Daar de pasgeboren kinderen van de meest
verschillende rassen lang zooveel niet in kleur verschillen als de
volwassenen, hebben wij eenige geringe aanwijzing, dat de tinten
der verschillende rassen werden verkregen na de verwijdering van
het haar, die, gelijk vroeger is medegedeeld, in een zeer vroeg
tijdperk moet hebben plaats gehad (8).

Overzicht.—Wij mogen besluiten, dat de meerdere grootte, kracht,


moed, strijdlustigheid en zelfs energie van den man, in vergelijking
met de zelfde hoedanigheden van de vrouw, gedurende
oorspronkelijke tijden werden verkregen en later zijn vermeerderd,
hoofdzakelijk door de gevechten tusschen de mannen die
wedijverden om het bezit der vrouwen. De grootere verstandelijke
kracht en uitvindende vermogens van den man zijn waarschijnlijk
ontstaan door natuurlijke teeltkeus, verbonden met de overgeërfde
gevolgen van de gewoonte; want de verstandigste mannen zullen er
het best in zijn geslaagd om zich zelven, hun vrouwen en kinderen
te verdedigen en te onderhouden. Zoover de verbazend groote
ingewikkeldheid van het onderwerp ons toelaat te oordeelen, schijnt
het, dat onze mannelijke op apen gelijkende voorouders hun
baarden verkregen als een sieraad om de tegenovergestelde sekse
te bekoren en op te wekken, en hen overplantten op den man, gelijk
hij nu bestaat. De wijfjes werden, naar het schijnt, het eerst op
gelijksoortige wijze als seksueel sieraad van haar ontbloot; maar zij
plantten dit kenmerk bijna gelijkelijk op beide seksen over. Het is
niet onwaarschijnlijk [375]dat de wijfjes ook in andere opzichten met
het zelfde doel en door de zelfde middelen werden gewijzigd, zoodat
vrouwen liefelijker stemmen hebben verkregen en schooner zijn
geworden dan de mannen.

Het verdient bijzondere oplettendheid, dat bij den mensch al de


voorwaarden voor seksueele teeltkeus veel gunstiger waren
gedurende een zeer vroeg tijdvak, toen de mensch nog slechts even
tot de menschelijke waardigheid was opgeklommen, dan gedurende
latere tijden. Want hij zal toen, gelijk wij veilig mogen besluiten,
meer door zijn instinktmatige hartstochten en minder door zorg voor
de toekomst of rede zijn geleid. Hij zal toen niet zoo uiterst
losbandig zijn geweest, als vele wilden nu zijn, en elke man zal zijn
vrouw of vrouwen ijverzuchtig hebben bewaakt. Hij zal toen geen
kindermoord hebben uitgeoefend, noch zijn vrouwen alleen als
bruikbare slavinnen gewaardeerd, noch aan haar zijn verloofd,
terwijl zij nog kinderen waren. Wij mogen daaruit afleiden dat de
menschenrassen, voor zoover de seksueele teeltkeus aangaat,
hoofdzakelijk gedurende een zeer verwijderd tijdvak werden
gedifferentieerd; en dit besluit werpt licht op het merkwaardige feit,
dat in het oudste tijdperk waarvan wij tot dusver eenig bericht
hebben verkregen, de menschenrassen er reeds toe waren gekomen
om bijna evenveel of volkomen evenveel van elkander te verschillen,
als zij op den huidigen dag doen.

De meeningen, hier voorgedragen over de rol die de seksueele


teeltkeus in de geschiedenis van den mensch heeft gespeeld,
hebben gebrek aan wetenschappelijke nauwkeurigheid. Hij die de
werking van dit beginsel niet aanneemt in het geval der lagere
dieren, zal al wat ik in de laatste hoofdstukken over den mensch heb
gezegd, waarschijnlijk gering schatten. Wij kunnen niet stellig
zeggen, dat dit kenmerk daardoor is gewijzigd, maar dat niet; echter
is aangetoond, dat de menschenrassen van elkander en van hun
naaste verwanten onder de lagere dieren verschillen in zekere
kenmerken die hun van geen dienst zijn voor de gewone doeleinden
van het leven en van welke het uiterst waarschijnlijk is, dat zij door
seksueele teeltkeus zijn gewijzigd. Wij hebben gezien, dat bij de
laagste wilden de menschen van elken stam hun eigen kenmerkende
hoedanigheden,—de gedaante van hun hoofd en gelaat, den
vierkanten vorm van hun jukbeenderen, het vooruitsteken of de
platheid van hun neus, de kleur van hun huid, de lengte van hun
hoofdhaar, het ontbreken van haar op het gelaat en het lichaam, of
de aanwezigheid van een grooten baard en zoo voorts,—
[376]bewonderen. Het kon daarom moeilijk missen, of deze en
andere dergelijke punten moesten langzamerhand en allengs
worden overdreven, omdat de machtigste en bekwaamste mannen
in elken stam, die er in moesten slagen het grootste aantal kinderen
groot te brengen, gedurende vele generaties de in de hoogste mate
aldus gekenmerkte en daarom aantrekkelijkste vrouwen voor het
huwelijk hebben uitgezocht. Ik voor mij kom tot het besluit, dat van
al de oorzaken die hebben geleid tot de verschillen in uiterlijk
aanzien tusschen de menschenrassen, en tot op zekere hoogte
tusschen den mensch en de lagere dieren, de seksueele teeltkeus
verreweg de werkzaamste is geweest.

[Inhoud]

AANTEEKENINGEN.

(1) Evenals de „opeischer” in Drenthe. Men vergelijke het hoogst


interessante stukje van M. M. Cohen Jr., „Een Drenthsche bruiloft ten
platten lande” in den Nieuwen Drenthschen Volksalmanak voor 1884,
blz. 241.

(2) Dit kunnen wij Darwin volstrekt niet toegeven; wij zien volstrekt
niet in, waarom de mannen, als zij zich met geweld van hun
vrouwen meester maakten, niet liever de schoonste vrouwen, of de
vrouwen die hun in eenig ander opzicht het aantrekkelijkst
toeschenen, zouden hebben geroofd dan de minder schoone en de
minder aantrekkelijke. Wij houden het zelfs voor hoogst
waarschijnlijk, dat zij een dergelijke voorkeur toonden.
(3) „Taboe”. Dit woord drukt een betrekking tot de goden of een
uitsluiting van gewone doeleinden, en tevens een bijzondere
bestemming van taboe verklaarde personen, plaatsen of zaken uit.
Het hangt met den godsdienst der Polynesiërs samen en was zoowel
op de Sandwich-eilanden, als op Otaheite, Noukahiva, Nieuw-
Zeeland enz. in gebruik. Zoo waren bij voorbeeld het vleesch van
verschillende dieren en bijna alle tot offeren bestemde zaken taboe
ten gebruike van de goden en de mannen; de vrouwen waren
derhalve van het genot er van uitgesloten. Soms werd een eiland of
landstreek taboe verklaard, waarna geen vaartuig of persoon zich
derwaarts mocht begeven. In de Israëlietische paradijslegende vindt
men het zelfde begrip. De vrucht van den boom der kennis des
goeds en des kwaads was taboe ten gebruike van Jahve en daarom
mochten Adam en Eva er niet van eten. Dat een meisje taboe werd
verklaard voor een of ander opperhoofd, beteekent dus, dat zij
uitsluitend voor zijn gebruik werd bestemd, en niemand haar kon
huwen, zonder heiligschennis te begaan. Op het breken van de
taboe stond de dood.

(4) Bovenal wordt dit bewezen, doordat die apen die plekken
blijkbaar bewonderen, er mede pronken en bij wijze van groet aan
den beschouwer toedraaien. Zie Darwin’s supplementaire
aanteekening, blz. 306.

(5) Vergelijk aant. 2, Deel I, blz. 81 ↗️.

(6) 1 Cor. XI, 15; hij voegt er echter bij: „omdat het lang hair voor
een decksel haer is gegeven”, en zegt ook (ibid., vers 14): „dat soo
een man langh hair draeght, het hem oneere is.”

(7) Of dit ook bij alle vroegere volken zoo is geweest, schijnt min of
[377]twijfelachtig. Op de oude Egyptische monumenten (en, naar
men verzekert ook op de bouwvallen van Yucatan en Chiapas) zijn
de mannen standvastig bruin of rood, de vrouwen geel gekleurd.
Waarschijnlijk moet dit echter op een andere wijze worden
verklaard, dan door een werkelijk verschil in kleur tusschen de beide
seksen bij de oude Egyptenaren en Amerikanen.

(8) Emil Decker, een Duitscher, die langen tijd verblijf hield in Noord-
Amerika, meent, dat het roode ras dat zich in de streken van het
Alleghany-gebergte heeft gevormd, daar vooral door de kleur der
omgeving de gele kleur zijner stamouders heeft verloren. Alles is
daar rood; roodbruin is er het ijzerhoudend zand en de
ijzerhoudende leem; roodbruin de stammen der pijnboomen, ceders
en eiken; rood of roodbruin de herten, vossen en konijnen.

Voor een volk dat van de jacht moest leven, was het een voordeel
de zelfde kleur te bezitten om meer ongemerkt het wild te kunnen
naderen en veiliger te zijn voor roofdieren.

Volgens deze onderstelling zou de roode kleur der inboorlingen van


Amerika dus een aanpassing zijn aan de kleur der omgeving.

Omtrent Afrika merkt Decker ter loops op, dat daar eveneens een
zekere kleurenharmonie in de verschillende voorwerpen valt waar te
nemen. Zoo zijn de gorilla, chimpanzee en groote dikhuidige dieren
zwart, evenals de neger. Omtrent de Maleiers en den orang-oetan
had hij een soortgelijke opmerking kunnen maken. De gele kleur der
Chineezen is in treffende harmonie met den gelen löss-grond, de
gele rivieren, gele planten en dieren. Omtrent de
woestijnbewonende Arabieren (en hun Semitische stamverwanten)
zou hij eveneens zulk een opmerking hebben kunnen maken.

Omtrent het Arische ras merkt R. E. de Haan („Alb. d. Natuur”, 1888,


blz. 38, waar hij een referaat omtrent Decker’s hypothese geeft) op,
dat de blanke kleur geen aanpassing kan zijn aan de witte
poolgewesten, die naar de meest recente meeningen de bakermat
van dat ras zouden zijn geweest. [378]
1 „Schopenhauer and Darwinism.”, in „Journal of Anthropology”, Jan. 1871, blz.
323. ↑
2 Deze aanhalingen zijn ontleend aan Lawrence („Lectures on Physiology” enz.,
1822, blz. 393) die de schoonheid der hoogere klassen in Engeland hieraan
toeschrijft, dat zij gedurende langen tijd de schoonste vrouwen voor het huwelijk
hebben uitgekozen. ↑
3 „Anthropologie”, „Revue des Cours Scientifiques”, Oct. 1868, blz. 721. ↑
4 „Het Varieeren der Huisdieren en Cultuurplanten”, Ned. vert., Deel II, blz.
220. ↑
5 Sir J. Lubbock, „The Origin of Civilisation”, 1870, chap. III, vooral blz. 60–67.
De heer M’Lennan spreekt in zijn uiterst belangrijk werk over „Primitive
Marriage”, 1865, blz. 163, van de vereeniging der beide seksen „in de vroegste
tijden als los, voorbijgaand en in zekere mate algemeen.” De heer M’Lennan en Sir
J. Lubbock hebben vele bewijzen bijeenverzameld van de uitermate groote
losbandigheid der wilden in den tegenwoordigen tijd. De heer L. H. Morgan besluit
in zijn belangwekkende verhandeling over het klassificatorische stelsel van
bloedverwantschap („Proc. American Acad. of Sciences”, vol. VII, Febr. 1868, blz.
475), dat gedurende de oorspronkelijke tijden de veelwijverij (polygamie) en alle
vormen van huwelijk wezenlijk onbekend waren. Uit Sir J. Lubbock’s werk blijkt
ook, dat Bachofen insgelijks gelooft, dat oorspronkelijk de communale vermenging
heeft geheerscht. ↑
6 „Address to British Association.” „On the Social and Religious Condition of the
Lower Races of Man”, 1870, blz. 20. ↑
7 „Origin of Civilisation”, 1870, blz. 86. In de onderscheidene boven aangehaalde
werken zal men overvloedige getuigenissen vinden omtrent de verwantschap
uitsluitend door de vrouwelijke linie of alleen met den stam. ↑
8 De heer C. Staniland Wake redeneert sterk („Anthropologia”, Maart 1874, blz.
197) tegen de meeningen van drie schrijvers omtrent het vroeger heerschen
van bijna algemeene seksueele vermenging (promiscuïteit); en hij meent, dat het
klassificatorische stelsel van bloedverwantschap op andere wijze kan worden
verklaard. ↑
9 Brehm („Illustr. Thierleben”, Bd. I, blz. 77) zegt, dat Cynocephalus hamadryas
in groote troepen leeft, die tweemaal zooveel volwassen wijfjes als volwassen
mannetjes bevatten. Zie Rengger over Amerikaansche veelwijvige (polygame)
soorten, en Owen („Anat. of Vertebrates”, vol. III, blz. 746) over Amerikaansche
eenwijvige (monogame) soorten. Nog andere aanhalingen zouden hierbij kunnen
worden gevoegd. ↑
10 Dr. Savage, in „Boston Journal of Nat. Hist.”, vol. V, 1845–47, blz. 423. ↑
11 „Prehistoric Times”, 1869, blz. 424. ↑
12Dr. Gerland („Ueber das Aussterben der Naturvölker”, 1868) heeft vele
mededeelingen over kindermoord verzameld, zie voornamelijk blz. 27, 52, 54.
Azara („Voyages” enz., tome II, blz. 94, 116) behandelt uitvoerig de
beweegredenen. Zie ook M’Lennan (ibid., blz. 139) omtrent gevallen in Indië. ↑
13 „Primitive Marriage”, blz. 208; Sir J. Lubbock, „Origin of Civilisation”, blz. 100.
Zie ook den heer Morgan, loc. cit., omtrent het vroeger heerschen van
veelmannerij (polyandrie). ↑
14 „Voyages” enz., tome II, blz. 92–95. ↑
15 Burchell zegt („Travels in South Africa”, vol. II, 1824, blz. 38), dat onder de
wilde volken van Zuid-Afrika, noch mannen noch vrouwen ooit hun leven in den
ongehuwden staat doorbrengen. Azara („Voyages dans l’Amérique Mérid.”, tome
II, 1806, blz. 21) maakt de zelfde opmerking omtrent de wilde Indianen van Zuid-
Amerika. ↑
16 „Anthropological Review”, Jan. 1870, blz. XVI. ↑
17 „Het Varieeren der Huisdieren en Cultuurplanten”, Ned. vert., deel II, blz. 224–
231. ↑
18 Een vernuftig schrijver beweert, op grond van een vergelijking van de
schilderijen van Raphael, Rubens en moderne Fransche kunstenaars, dat het
denkbeeld van schoonheid zelfs niet door Europa heên het zelfde is; zie de „Lives
of Haydn and Mozart” door M. Bombet, Eng. vert., blz. 278. ↑
19 „Proc. R. Geogr. Soc.”, vol. XV, blz. 47. ↑
20 McKennan, aangehaald door den heer Wake in „Anthropologia”, Oct. 1873, blz.
75. ↑
21 D. Lesley, „Kafir Character and Customs”, 1871, blz. 4. ↑
22 Azara, „Voyages” enz, tome II, blz. 23. Dobrizhoffer, „An Account of the
Abipones”, vol. II, 1822, blz. 207. Williams over de Fidsji-eilanders, aangehaald
door Lubbock, „Origin of Civilisation”, 1870, blz. 79. Omtrent de Vuurlanders, King
en Fitzroy, „Voyages of the Adventure and Beagle”, vol. II, 1839, blz. 121. Omtrent
de Kalmukken, de aanhaling van M’Lennan, „Primitive Marriage”, 1865, blz. 32.
Omtrent de Maleiers, Lubbock, ibid., blz. 77. De weleerw. heer J. Shooter, „On the
Kafirs of Natal”, 1857, blz. 52–60. Omtrent de Bosjesmannen, Burchell, „Travels in
S. Africa”, vol. II, 1824, blz. 50. ↑
23 „Contributions to the Theory of Natural Selection”, 1870, blz. 346. De heer
Wallace gelooft (blz. 350), „dat de eene of andere intelligente kracht de
ontwikkeling van den mensch heeft geleid of bepaald”; en hij beschouwt den
haarloozen toestand der huid als een deze meening steunend feit. De weleerw.
heer T. R. Stebbing merkt, commentariën op deze meening makende
(„Transactions of Devonshire Assoc. for Science”, 1870), op, dat, wanneer de heer
Wallace „zijn gewone scherpzinnigheid in het vraagstuk van de onbehaardheid van
’s menschen huid had gebruikt, hij wellicht de mogelijkheid zou hebben ingezien,
dat zij wegens haar meerdere schoonheid of wegens de gezondheid die het gevolg
van meerdere zindelijkheid is, kon zijn gekozen. In elk geval is het bevreemdend,
dat hij zich zelfs een hoogere intelligentie kan voorstellen, bezig met haar uit de
ruggen te plukken (voor welke het volgens zijn eigen schildering nuttig en noodig
zou zijn geweest), opdat de afstammelingen van die arme geschoren drommels na
vele sterfgevallen ten gevolge van koude en vochtigheid in den loop van vele
generaties”, zouden zijn gedwongen hooger te stijgen op de ladder der beschaving
door de uitoefening van onderscheidene kunsten op de door den heer Wallace
aangegeven wijze. ↑
24 „Het Varieeren der Huisdieren en Cultuurplanten”, Ned. vert., Deel II, blz.
380. ↑
25 „Investigations into Military and Anthropological Statistics of American Soldiers”,
door B. Gould, 1869, blz. 568.—Er werden zorgvuldige waarnemingen gedaan
omtrent de behaardheid van 2129 zwarte en gekleurde soldaten, terwijl zij zich
baadden: en als men de uitgegeven tabel inziet, „is het bij den eersten blik reeds
duidelijk, dat er in dit opzicht slechts weinig, indien eenig, verschil tusschen en de
blanke en zwarte rassen is.” Het is echter zeker, dat de negers in hun
oorspronkelijk en veel warmer vaderland Afrika opmerkelijk gladde lichamen
hebben. Men moet er bijzonder acht op geven, dat onder bovengenoemde
optelling zoowel zuivere zwarten als mulatten waren begrepen; en dit is een
ongelukkige omstandigheid, daar volgens het beginsel waarvan ik elders de
waarheid heb bewezen, gekruiste rassen uiterst vatbaar zullen zijn om door
atavisme tot den oorspronkelijken harigen toestand van hun vroegen, op een aap
gelijkenden voorvader terug te keeren. ↑
26 „Ueber die Richtung der Haare am menschlichen Körper” in Müller’s „Archiv für
Anat. und Phys.”, 1837, blz. 40. ↑
27 De heer Sproat („Scenes and Studies of Savage Life”, 1868, blz. 25) oppert ten
opzichte van baardelooze inboorlingen van Vancouver’s Eiland het vermoeden,
dat de gewoonte om de haren op het gelaat uit te trekken, „van generatie tot
generatie voortgezet, wellicht ten laatste een ras zou voortbrengen, dat zich door
een dunnen en verstrooiden baardgroei zou onderscheiden.” De gewoonte zou
echter niet zijn ontstaan, tenzij de baard reeds ten gevolge van een of andere
daarvan onafhankelijke oorzaak zeer was afgenomen. Wij hebben ook geen enkel
direct bewijs, dat het bestendig uittrekken van het haar tot eenig erfelijk gevolg
zou leiden. Ten gevolge van deze oorzaak van twijfel, heb ik tot dusverre nog geen
gewag gemaakt van de door vele uitstekende ethnologen, bij voorbeeld den heer
Gosse van Genève, verdedigde meening, dat kunstmatige wijzigingen van den
schedel een neiging tot erfelijkheid hebben. Ik wensch dit besluit niet te
bestrijden; en wij weten tegenwoordig door Dr. Brown-Séquard’s merkwaardige
waarnemingen, vooral die welke onlangs (1870) aan de British Association zijn
medegedeeld, dat bij Guineesche biggetjes de gevolgen van operaties erfelijk zijn
(5). ↑
28 „Ueber die Richtung”, ibid., blz. 40. ↑
29 „On the tail-feathers of Motmots”, „Proc. Zoolog. Soc.”, 1873, blz. 429. ↑
30De heer Sproat („Scenes and Studies of Savage Life”, 1868, blz. 25) oppert
deze zelfde meening. Eenige voorname ethnologen, onder anderen de heer
Gosse van Genève, meenen, dat kunstmatige wijzigingen van den schedelvorm
een neiging hebben om erfelijk te worden. ↑
[Inhoud]
EEN-EN-TWINTIGSTE HOOFDSTUK.
ALGEMEEN OVERZICHT EN BESLUIT.

Hoofdbesluit: de mensch stamt af van den eenen of anderen lageren


vorm.—Wijze van ontwikkeling.—Stamboom van den mensch.—
Verstandelijke en zedelijke vermogens.—Seksueele teeltkeus.—
Slotaanmerkingen.

Een kort overzicht zal hier voldoende zijn om den lezer de


voornaamste punten van dit werk opnieuw voor den geest te
brengen. Vele der beschouwingen die zijn gemaakt, zijn in hooge
mate bespiegelend (speculatief), en sommige er van zullen
ongetwijfeld blijken onjuist te zijn; maar ik heb in elk geval de
redenen gegeven, die er mij toe hebben gebracht aan de eene
beschouwingswijze de voorkeur te geven boven de andere. Het
scheen mij de moeite waard te beproeven, in hoever het beginsel
van ontwikkeling (evolutie) licht zou werpen op eenige der meer
ingewikkelde vraagstukken in de natuurlijke geschiedenis van den
mensch. Valsche feiten zijn in hooge mate nadeelig voor den
vooruitgang der wetenschap, want men blijft ze soms gedurende
langen tijd aannemen; maar valsche beschouwingen, als zij door
eenige bewijzen worden gesteund, doen weinig schade, daar
iedereen er een heilzaam vermaak in schept om haar valschheid te
bewijzen; en als dit is gedaan, is één pad naar de dwaling gesloten
en dikwijls tegelijkertijd de weg naar de waarheid geopend.

Het hoofdbesluit waartoe ik in dit werk ben gekomen en dat op dit


oogenblik wordt gesteund door vele natuuronderzoekers die zeer
bevoegd zijn om een gezond oordeel te vellen, is, dat de mensch
afstamt van den eenen of anderen minder hoog georganiseerden
vorm. De gronden waarop dit besluit rust, zullen nooit worden
geschokt; want de groote overeenkomst tusschen den mensch en de
lagere dieren in embryonale ontwikkeling, zoowel als in tallooze
punten van maaksel en gestel, zoowel van hoog belang als van den
onbeduidendsten aard,—[379]de rudimenten welke hij heeft
behouden, en de abnormale atavismen waaraan hij nu en dan
onderhevig is,—zijn feiten die niet kunnen worden betwist. Zij zijn
lang bekend geweest; maar tot voor korten tijd zeiden zij ons niets
ten opzichte van den oorsprong van den mensch. Tegenwoordig
echter, als men ze beschouwt in het licht van onze kennis van de
geheele organische wereld, is hun beteekenis onmiskenbaar. Het
groote beginsel van ontwikkeling (evolutie) staat helder en vast voor
ons, als deze groepen van feiten worden beschouwd in verband met
andere, zooals de wederkeerige verwantschap van de leden van
ééne en de zelfde groep, hun geographische verspreiding in
vroegeren en tegenwoordigen tijd en hun geologische opeenvolging.
Het is ongeloofbaar, dat al deze feiten onwaarheid zouden spreken.
Hij die niet gelijk een wilde tevreden is met de natuurverschijnselen
te beschouwen als in geen verband met elkander staande, kan geen
oogenblik langer gelooven, dat de mensch het voortbrengsel van
een afzonderlijke scheppingshandeling is. Hij zal genoodzaakt zijn
toe te geven, dat de groote gelijkenis tusschen het embryo van een
mensch en dat, bij voorbeeld, van een hond;—het maaksel van zijn
schedel, ledematen en geheele lichaam, onafhankelijk van de
gebruiken waartoe de deelen kunnen worden aangewend, volgens
het zelfde plan als die van andere zoogdieren,—het nu en dan
opnieuw verschijnen van onderscheidene inrichtingen, bij voorbeeld
van verscheidene verschillende spieren die de mensch normaal niet
bezit, maar aan de Vierhandige Zoogdieren (Quadrumana) gemeen
zijn,—en een menigte overeenkomstige feiten,—allen op de
duidelijkste wijze op het besluit wijzen, dat de mensch met andere
zoogdieren de medeafstammeling van een gemeenschappelijken
stamvader is.

Wij hebben gezien, dat de mensch onophoudelijk individueele


verschillen in alle deelen van zijn lichaam en in zijn geestvermogens
vertoont. Deze verschillen of afwijkingen (variaties) schijnen het
gevolg te zijn van de zelfde algemeene oorzaken, en te
gehoorzamen aan de zelfde algemeene wetten als bij de lagere
dieren. In beide gevallen heerschen gelijksoortige wetten van
erfelijkheid. De mensch heeft een neiging om zich in sterker
verhouding te vermenigvuldigen dan zijn middelen van bestaan; bij
gevolg is hij nu en dan onderworpen aan een hevigen strijd om het
bestaan (1), en zal de natuurlijke teeltkeus hebben uitgewerkt,
hetgeen binnen de grenzen harer werkzaamheid ligt. Een
opeenvolging van sterk uitgedrukte afwijkingen (variaties) van
gelijksoortigen [380]aard is in geenen deele noodzakelijk; geringe
fluctueerende verschillen in het individu zijn voldoende voor het
werk der natuurlijke teeltkeus. Wij mogen ons overtuigd houden, dat
de overgeërfde gevolgen van het lang voortgezet gebruiken of niet-
gebruiken van deelen veel zal hebben gedaan in de zelfde richting
als de natuurlijke teeltkeus. Wijzigingen, vroeger van belang, hoewel
niet langer van eenig bijzonder nut, zullen nog langen tijd worden
overgeërfd. Als één deel is gewijzigd, zullen andere deelen zich
wijzigen volgens het beginsel van correlatieve monstruositeiten. Ook
mag iets worden toegeschreven aan de directe en bepaalde werking
van de omringende levensvoorwaarden, zooals overvloedig voedsel,
hitte of vochtigheid; en eindelijk zijn vele kenmerken van geringe
physiologische belangrijkheid, doch ook sommige van aanmerkelijke
belangrijkheid, door seksueele teeltkeus verkregen.

Ongetwijfeld vertoont de mensch, zoo goed als elk ander dier,


inrichtingen die, voor zoover wij met onze geringe kennis kunnen
oordeelen, hem tegenwoordig van volstrekt geen dienst zijn, en dat
ook niet zijn geweest gedurende eenig vroeger tijdperk van zijn
bestaan hetzij ten opzichte van zijn algemeene levensvoorwaarden
of van de betrekking van de eene sekse tot de andere. Dergelijke
inrichtingen kunnen niet worden verklaard door eenigen vorm van
teeltkeus, noch door de overgeërfde gevolgen van het gebruiken en
niet-gebruiken van deelen. Wij weten echter, dat vele vreemde en
sterk uitgedrukte bijzonderheden van maaksel zich nu en dan bij
onze huisdieren vertoonen, en indien de onbekende oorzaken die ze
voortbrengen, er toe kwamen om meer eenvormig te werken,
zouden zij waarschijnlijk gemeen worden aan al de individu’s van de
soort. Wij mogen hopen, dat wij later iets zullen begrijpen van de
oorzaken van dergelijke nu en dan voorkomende wijzigingen,
voornamelijk door de studie van monstruositeiten; daarom is de
arbeid van proefnemende natuuronderzoekers, zooals die van
Camille Dareste (2), vol van belofte voor de toekomst. In het
grootste aantal gevallen kunnen wij alleen zeggen, dat de oorzaak
van elke geringe afwijking (variatie) en van elke monstruositeit veel
meer ligt in den aard en het gestel van het organisme, dan in den
aard der omringende levensvoorwaarden: hoewel nieuwe en
veranderde levensvoorwaarden ongetwijfeld een belangrijk aandeel
hebben in het opwekken van organische veranderingen van allerlei
aard.

Door de juist opgenoemde middelen, wellicht geholpen door andere,


[381]die nog niet zijn ontdekt, is de mensch opgeklommen tot zijn
tegenwoordigen toestand. Doch sedert hij den rang van mensch
heeft bereikt, heeft hij zich in onderscheidene rassen, of, gelijk zij
nog gepaster kunnen worden genoemd, onder-soorten (sub-species)
gesplitst. Sommige daarvan, bij voorbeeld de Neger en de
Europeeër, verschillen zoozeer van elkander, dat, indien voorwerpen
er van zonder eenige nadere toelichting aan een natuuronderzoeker
waren gebracht, zij ongetwijfeld door hem als goede en ware
soorten zouden zijn beschouwd. Desniettemin komen alle rassen
overeen in zoovele onbelangrijke kleine bijzonderheden van maaksel
en in zoovele geestelijke eigenaardigheden, dat men deze alleen kan
verklaren door overerving van een gemeenschappelijken stamvader,
en een stamvader welke die kenmerken bezat, zou waarschijnlijk
aanspraak hebben mogen maken op den rang van mensch.
Men mag niet veronderstellen, dat de afscheiding van elk ras van de
andere rassen, en van al de rassen van een gemeenschappelijken
stam kan worden vervolgd, tot men eindelijk op één enkel paar
stamouders stuit. Integendeel zullen op elken trap van het proces
van wijziging al de individu’s die op eenige wijze, hoewel in
verschillende mate het best geschikt waren voor hun
levensvoorwaarden, in grooter getal in leven zijn gebleven dan de
minder goed geschikte. Het proces zal het zelfde zijn geweest als dat
hetwelk de mensch volgt, als hij bij de voortplanting zijner
huisdieren niet met voordacht bijzondere individu’s uitzoekt, maar
toch al de uitnemende voor de fokkerij gebruikt en al de minder
uitnemende veronachtzaamt. Hij wijzigt zoo langzaam, maar zeker,
zijn vee en vormt onbewust een nieuw ras. Evenzoo zal, wat
wijzigingen aangaat, onafhankelijk van teeltkeus verkregen, en die
het gevolg zijn van afwijkingen, ontspruitende uit den aard van het
organisme en de werking der omringende levensvoorwaarden of uit
een veranderde levenswijze, één enkel paar niet in veel grooter mate
zijn gewijzigd dan de andere paren die het zelfde land bewoonden;
want allen zullen zich onophoudelijk door vrije kruising met elkander
hebben vermengd.

Als wij het embryologische maaksel van den mensch, de


homologieën met de lagere dieren, die hij vertoont,—de rudimenten
die hij heeft behouden,—en de atavismen waaraan hij onderhevig is,
beschouwen, kunnen wij ons in onze verbeelding den vroegeren
toestand onzer voormalige stamouders gedeeltelijk voorstellen, en
kunnen wij bij benadering de hun toekomende plaats in de reeks van
het Dierenrijk aanwijzen. [382]Wij leeren dan, dat de mensch afstamt
van een behaard viervoetig dier, voorzien van een staart en puntige
ooren, waarschijnlijk in de boomen levende en een bewoner van de
Oude Wereld. Dit schepsel zou, indien zijn geheele maaksel door een
natuuronderzoeker was onderzocht, door dezen tot de Apen of
Vierhandige Zoogdieren (Quadrumana) zijn gebracht, met evenveel
zekerheid als de gemeenschappelijke en nog oudere stamvader van
de Apen der Oude en Nieuwe Wereld. De Vierhandigen
(Quadrumana) en al de hoogere Zoogdieren stammen waarschijnlijk
af van een oud Buideldier, en dit, door een lange lijn van
verschillende vormen, hetzij van een of ander Reptielachtig of een
Amphibieachtig schepsel, en dit op zijn beurt van een of ander
Vischachtig dier. In de dikke duisternis van het verleden kunnen wij
zien, dat de voormalige stamvader van al de Gewervelde Dieren een
waterbewonend dier moet zijn geweest, voorzien van kieuwen, bij
hetwelk beide seksen in het zelfde individu vereenigden de
belangrijkste deelen van het lichaam (zooals de hersenen en het
hart) onvolkomen ontwikkeld moeten zijn geweest. Dit dier schijnt
meer overeenkomst te hebben gehad met de larven der nog
bestaande en in zee levende Zakpijpen (Ascidiae) dan met eenigen
anderen bekenden vorm.

De grootste moeilijkheid die zich opdoet, wanneer wij tot


bovengenoemd besluit omtrent den oorsprong van den mensch
worden gedreven, is de hooge standaard van verstandelijke kracht
en zedelijken aanleg, dien hij heeft bereikt. Iedereen die het
algemeene beginsel van ontwikkeling (evolutie) aanneemt, moet
echter inzien, dat de geestvermogens der hoogere dieren, die, wat
de hoedanigheid aangaat, de zelfde zijn als die van den mensch,
hoewel zij in hoeveelheid daarvan zooveel verschillen, voor
vooruitgang vatbaar zijn. Zoo is de afstand tusschen de
geestvermogens van een der hoogere apen en die van een visch, of
tusschen die van een mier en die van een schildluis verbazend groot.
De ontwikkeling dier vermogens bij dieren levert geen bijzondere
moeilijkheid op; want bij onze tamme dieren zijn de geestvermogens
ongetwijfeld aan afwijkingen onderhevig, en de afwijkingen worden
overgeërfd. Niemand betwijfelt, dat deze vermogens van het
hoogste belang zijn voor dieren in den natuurstaat. De
omstandigheden zijn derhalve gunstig voor hun ontwikkeling door
natuurlijke teeltkeus. Het zelfde besluit kan tot den mensch worden
uitgestrekt; het verstand moet voor hem uiterst belangrijk zijn
geweest, zelfs in een zeer verwijderd tijdperk, daar het hem in staat
stelde de spraak te gebruiken, [383]wapenen, werktuigen, vallen enz.
uit te vinden en te vervaardigen; door welke middelen, in verbinding
met zijn sociale levenswijze, hij lang geleden het meest heerschende
van alle levende schepselen werd.

Een groote stap in de ontwikkeling van het verstand zal zijn gedaan,
zoodra ten gevolge van een voorafgaanden aanmerkelijken
vooruitgang de half als kunst, half als instinkt te beschouwen spraak
in gebruik kwam; want het voortdurende gebruik van de spraak zal
op de hersenen hebben teruggewerkt en een erfelijke uitwerking
voortgebracht; en dit zal weder op den vooruitgang van de taal
hebben teruggewerkt. De aanzienlijke grootte van de hersenen van
den mensch, in vergelijking van die der hoogere dieren, naar
verhouding van de grootte hunner lichamen, kan voornamelijk
worden toegeschreven, gelijk de heer Chauncey Wright terecht heeft
opgemerkt 1, aan het vroege gebruik van den eenen of anderen vorm
van taal,—dat wondervolle werktuig dat aan alle voorwerpen en
hoedanigheden teekens toekent, en aaneenschakelingen van
gedachten opwekt, die nimmer zouden zijn ontstaan ten gevolge van
den blooten indruk der zinnen, en, al waren zij ontstaan, nimmer ten
einde toe zouden kunnen zijn vervolgd. De hoogere verstandelijke
vermogens van den mensch, zooals dat van redeneering, dat om
afgetrokken denkbeelden te vormen, de zelfbewustheid enz. zullen
zijn gevolgd uit de voortdurende verbetering van andere
geestvermogens; maar zonder aanmerkelijke veredeling van den
geest, zoowel bij het ras als bij het individu, is het twijfelachtig, of
deze hooge vermogens zouden zijn geoefend en daardoor in
volkomenheid verkregen.
De ontwikkeling der zedelijke vermogens is een belangwekkender en
moeilijker vraagstuk. Hun grond ligt in de sociale instinkten, wanneer
men in deze uitdrukking de familiebanden insluit. Deze instinkten
zijn van een hoogst ingewikkelden aard, en geven in het geval van
de lagere dieren bijzondere neigingen tot zekere bepaalde
handelingen; maar voor ons zijn de belangrijkste elementen liefde
en de daarvan te onderscheiden aandoening van medegevoel.
Dieren die met sociale instinkten zijn begaafd, scheppen behagen in
elkanders gezelschap, waarschuwen elkander voor gevaar,
verdedigen en helpen elkander op vele wijzen. Deze instinkten
worden niet tot alle individu’s van de soort, maar alleen tot die van
de zelfde vereeniging uitgestrekt. Daar [384]zij in hooge mate
voordeelig voor de soort zijn, zijn zij waarschijnlijk door natuurlijke
teeltkeus verkregen.

Een zedelijk wezen is een wezen dat in staat is om zijn verleden


handelingen en beweegredenen met zijn toekomstige te vergelijken,
—om sommige goed en andere af te keuren; en het feit, dat de
mensch het eenige wezen is, dat met zekerheid aldus kan worden
genoemd, vormt het grootste van alle verschillen tusschen hem en
de lagere dieren. In ons vierde hoofdstuk heb ik echter trachten aan
te toonen, dat het zedelijk gevoel volgt, ten eerste, uit den
duurzamen en altijd tegenwoordigen aard der sociale instinkten, in
welk opzicht de mensch met de lagere dieren overeenkomt; en ten
tweede, uit de groote werkzaamheid van zijn geestvermogens en de
uiterste levendigheid van zijn indrukken van vroegere
gebeurtenissen, in welke opzichten hij van de lagere dieren verschilt.
Ten gevolge van dezen toestand van zijn geest, kan de mensch zich
niet onttrekken aan het in het verleden terugzien en het vergelijken
van de indrukken van vroegere gebeurtenissen en handelingen. Hij
ziet ook voortdurend voorwaarts. Daarom zal hij, wanneer de eene
of andere tijdelijke begeerte of hartstocht zijn sociale instinkten
heeft overmeesterd, nadenken en de op dat oogenblik verzwakte
indrukken van dergelijke vroegere opwellingen met het altijd
tegenwoordige sociale instinkt vergelijken; en hij zal dan dat gevoel
van onvoldaanheid gevoelen, dat alle niet bevredigde instinkten
achter zich laten. Bij gevolg neemt hij het besluit om in de toekomst
anders te handelen,—en dit is geweten. Elk instinkt dat aanhoudend
sterker en duurzamer is, dan een ander, doet een gevoel ontstaan,
dat wij uitdrukken door te zeggen, dat het onze plicht is daaraan te
gehoorzamen. Een staande hond zou, indien hij in staat was over
zijn vroeger gedrag na te denken, tot zich zelf zeggen: het was mijn
plicht geweest (zooals wij inderdaad van hem zeggen) voor dien
haas te staan en niet toe te geven aan de voorbijgaande verzoeking
om hem na te zitten.

Sociale dieren worden gedeeltelijk aangedreven door den wensch


om de leden van de zelfde vereeniging op algemeene wijze te
helpen, maar veelvuldiger om zekere bepaalde handelingen te
verrichten. De mensch wordt aangedreven door den zelfden
algemeenen wensch om zijn medemenschen te helpen, maar heeft
weinig of geen bijzondere instinkten. Hij verschilt ook van de lagere
dieren, doordat hij in staat is zijn begeerten uit te drukken met
woorden die zoo naar de gevraagde en [385]verleende hulp
heênvoeren. De beweegreden om hulp te verleenen is ook
eenigszins gewijzigd bij den mensch; zij bestaat niet langer alleen uit
een blinde instinktmatige aandrift, maar de lof of afkeuring zijner
medemenschen heeft daarop grooten invloed. Zoowel het waarde
hechten aan lof of afkeuring als het betuigen daarvan berust op
medegevoel, en deze aandoening is, gelijk wij hebben gezien, een
der belangrijkste elementen van de sociale instinkten. Hoewel het
medegevoel als een instinkt wordt verkregen, wordt het toch door
oefening veel versterkt. Daar alle menschen hun eigen geluk
verlangen, wordt lof of afkeuring over daden en beweegredenen
betuigd, al naar zij tot dit doel leiden of niet; en daar geluk een
wezenlijk deel uitmaakt van het algemeen welzijn, dient het beginsel
van het grootste geluk indirect als een omtrent juiste maatstaf van
recht en onrecht. Naarmate de redeneerende vermogens
vooruitgaan en ondervinding wordt verkregen, worden de meer
verwijderde uitwerkselen van zekere gedragslijnen op het karakter
van het individu en op het algemeen welzijn begrepen; en dan
worden de op het individu zelf betrekking hebbende deugden, omdat
zij nu binnen het bereik der publieke opinie komen, geprezen en de
tegenovergestelde eigenschappen afgekeurd. Doch bij de minder
beschaafde volken geraakt de rede dikwijls op een dwaalspoor, en
worden vele slechte gewoonten en lage bijgeloovigheden op de
zelfde wijze beschouwd en derhalve als hooge deugden vereerd en
de overtreding er van voor een zware misdaad gehouden.

De zedelijke vermogens worden algemeen, en terecht, als van


hooger waarde dan de verstandelijke vermogens beschouwd. Wij
behooren echter steeds te bedenken, dat de werkzaamheid van den
geest in het levendig terugroepen van vroegere indrukken een der
fundamenteele, hoewel secundaire grondslagen van het geweten is.
Dit feit levert den sterksten bewijsgrond, dat men de verstandelijke
vermogens van elk menschelijk wezen op alle mogelijke wijzen
behoort aan te kweeken en op te wekken. Ongetwijfeld zal een
mensch met een tragen geest, indien zijn sociale neigingen en
medegevoel goed zijn ontwikkeld, tot goede handelingen worden
geleid, en kan hij een tamelijk gevoelig geweten hebben. Al wat
echter de verbeeldingskracht van den mensch levendiger maakt en
de gewoonte om vroegere indrukken terug te roepen en te
vergelijken, versterkt, zal het geweten gevoeliger maken, en kan
zelfs tot op zekere hoogte zwakke sociale neigingen en medegevoel
vergoeden. [386]

De zedelijke natuur van den mensch is opgeklommen tot den


hoogsten tot dusverre bereikten graad, gedeeltelijk door den
vooruitgang der redeneerende vermogens en bij gevolg van een
rechtvaardige publieke opinie, maar vooral doordat het medegevoel
teederder en in wijderen kring werd verspreid door de uitwerkselen
van gewoonte, voorbeeld, onderwijs en nadenken. Het is niet
onwaarschijnlijk, dat deugdzame neigingen door lange uitoefening
erfelijk kunnen worden. Bij de meer beschaafde rassen heeft de
overtuiging van het bestaan van een alziende Godheid een
machtigen invloed op den vooruitgang der zedelijkheid gehad. Ten
laatste neemt de mensch niet langer den lof of de afkeuring zijner
medemenschen als zijn voornaamsten gids aan, hoewel weinigen
aan den invloed daarvan ontsnappen, maar bieden hem zijn gewone
overtuigingen, onder toezicht van de rede, zijn veiligsten regel aan.
Zijn geweten wordt dan zijn opperste rechter en vermaner.
Desniettemin ligt de eerste grond of oorsprong van het zedelijk
gevoel in de sociale instinkten, met insluiting van het medegevoel,
en deze instinkten werden ongetwijfeld oorspronkelijk, evenals in het
geval der lagere dieren, verkregen door natuurlijke teeltkeus.

Het geloof in God is dikwijls niet alleen als het grootste, maar als het
volkomenste van alle verschillen tusschen den mensch en de lagere
dieren voorgesteld. Het is echter onmogelijk, gelijk wij hebben
gezien, om vol te houden, dat dit geloof bij den mensch aangeboren
of instinktmatig is. Daarentegen schijnt het geloof aan in alles
verspreide geestelijke invloeden algemeen te zijn, en schijnt het
gevolg te zijn van een aanmerkelijken vooruitgang in de
redeneerende vermogens van den mensch en van een nog grooter
vooruitgang in zijn vermogens van verbeelding, nieuwsgierigheid en
verwondering. Ik weet zeer goed, dat het vermeende instinktmatige
geloof aan God door vele personen is gebruikt als een bewijsgrond
voor Zijn bestaan. Dit is echter een overijlde bewijsgrond, daar wij
dan ook gedwongen zouden zijn in het bestaan te gelooven van vele
wreede en kwaadwillige geesten die slechts een weinig meer macht
dan de mensch bezitten; want het geloof in hen is veel algemeener
dan dat in een weldoende Godheid. Het denkbeeld van een
algemeen en weldoend Schepper van het Heelal schijnt niet in den
geest van den mensch op te komen, totdat hij door een lang
voortgezette beschaving is opgeheven.

Hij die gelooft, dat de mensch zich uit den eenen of anderen laag
[387]georganiseerden vorm heeft ontwikkeld, zal natuurlijk vragen
welken invloed dit heeft op het geloof aan de onsterfelijkheid van de
ziel. De barbaarsche menschenrassen bezitten, gelijk Sir J. Lubbock
heeft aangetoond, geen duidelijk geloof van deze soort; maar
bewijsgronden, aan de oorspronkelijke godsdienstige meeningen van
wilden ontleend, zijn, gelijk wij juist hebben gezien, van weinig of
geen nut. Weinige menschen gevoelen eenige bezorgdheid wegens
de onmogelijkheid om nauwkeurig te bepalen, op welk tijdstip in de
ontwikkeling van het individu, van het eerste spoor van het kleine
kiemblaasje af tot het kind, hetzij voor of na de geboorte toe, de
mensch een onsterfelijk wezen wordt; en er is geen grooter reden
tot bezorgdheid, omdat het juiste tijdstip in de trapsgewijze
opklimmende reeks der organismen bij geen mogelijkheid kan
worden bepaald. 2 (3)

Ik weet wel, dat de besluiten waartoe wij in dit werk zijn gekomen,
door sommigen van groote ongodsdienstigheid zullen worden
beschuldigd; maar hij, die hen daarvan beschuldigt, is verplicht aan
te toonen, waarom het ongodsdienstiger is om den oorsprong van
den mensch als een afzonderlijke soort door afstamming uit den
eenen of anderen lageren vorm te verklaren door de wetten van
afwijking (variatie) en natuurlijke teeltkeus, dan om de geboorte van
het individu door de wetten van de gewone voortplanting te
verklaren. De geboorte zoowel van de soort als van het individu zijn
gelijkelijk deelen van die groote aaneenschakeling van
gebeurtenissen die onze geest weigert als gevolgen van een blind
Welcome to our website – the ideal destination for book lovers and
knowledge seekers. With a mission to inspire endlessly, we offer a
vast collection of books, ranging from classic literary works to
specialized publications, self-development books, and children's
literature. Each book is a new journey of discovery, expanding
knowledge and enriching the soul of the reade

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.

Let us accompany you on the journey of exploring knowledge and


personal growth!

ebookmass.com

You might also like