[go: up one dir, main page]

100% found this document useful (1 vote)
147 views55 pages

Go Optimizations 101 Tapir Liu 2024 scribd download

Tapir

Uploaded by

boneycampsnd
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 (1 vote)
147 views55 pages

Go Optimizations 101 Tapir Liu 2024 scribd download

Tapir

Uploaded by

boneycampsnd
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/ 55

Download the Full Version of textbook for Fast Typing at textbookfull.

com

Go Optimizations 101 Tapir Liu

https://textbookfull.com/product/go-optimizations-101-tapir-
liu/

OR CLICK BUTTON

DOWNLOAD NOW

Download More textbook Instantly Today - Get Yours Now at textbookfull.com


Recommended digital products (PDF, EPUB, MOBI) that
you can download immediately if you are interested.

Buddhism 101 Arnold Kozak

https://textbookfull.com/product/buddhism-101-arnold-kozak/

textboxfull.com

Black Hat Go Go Programming For Hackers And Pentesters Tom


Steele

https://textbookfull.com/product/black-hat-go-go-programming-for-
hackers-and-pentesters-tom-steele/

textboxfull.com

Power Play Go Big or Go Home 1st Edition Rosemary


Willhide

https://textbookfull.com/product/power-play-go-big-or-go-home-1st-
edition-rosemary-willhide/

textboxfull.com

World History 101 Tom Head

https://textbookfull.com/product/world-history-101-tom-head/

textboxfull.com
Go. No Go. Is Your Business Idea Ready to Launch? Lauri
Harrison

https://textbookfull.com/product/go-no-go-is-your-business-idea-ready-
to-launch-lauri-harrison/

textboxfull.com

Black Hat Go Go Programming For Hackers and Pentesters 1st


Edition Tom Steele

https://textbookfull.com/product/black-hat-go-go-programming-for-
hackers-and-pentesters-1st-edition-tom-steele/

textboxfull.com

Let s Go Learn to build professional web applications with


Go Alex Edwards

https://textbookfull.com/product/let-s-go-learn-to-build-professional-
web-applications-with-go-alex-edwards/

textboxfull.com

Let s Go Learn to build professional web applications with


Go Alex Edwards

https://textbookfull.com/product/let-s-go-learn-to-build-professional-
web-applications-with-go-alex-edwards-2/

textboxfull.com

Real Estate Investing 101 Michele Cagan

https://textbookfull.com/product/real-estate-investing-101-michele-
cagan/

textboxfull.com
Acknowledgments
Firstly, thanks to the entire Go community. An active and responsive
community ensured this book was finished on time.

Specially, I want to give thanks to the following people who helped


me understand some details in the official standard compiler and
runtime implementations: Keith Randall, Ian Lance Taylor, Axel
Wagner, Cuong Manh Le, Michael Pratt, Jan Mercl, Matthew
Dempsky, Martin Möhrmann, etc. I'm sorry if I forgot mentioning
somebody in the above list. There are so many kind and creative
gophers in the Go community that I must have missed out on
someone.

I also would like to thank all gophers who ever made influences on
this book, be it directly or indirectly, intentionally or unintentionally.

Thanks to Olexandr Shalakhin for the permission to use one of the


wonderful gopher icon designs as the cover image. And thanks to
Renee French for designing the lovely gopher cartoon character.

Thanks to the authors of the following open source software and


libraries used in building this book:

golang, https://go.dev/
gomarkdown, https://github.com/gomarkdown/markdown
goini, https://github.com/zieckey/goini
go-epub, https://github.com/bmaupin/go-epub
pandoc, https://pandoc.org
calibre, https://calibre-ebook.com/
GIMP, https://www.gimp.org

Thanks the gophers who ever reported mistakes in this book or


made corrections for this book: yingzewen, ivanburak, cortes-,
skeeto@reddit, Yang Yang, DashJay, Stephan, etc.
About Go Optimizations 101
This book offers practical tricks, tips, and suggestions to optimize Go
code performance. Its insights are grounded in the official Go
compiler and runtime implementation.

Life is full of trade-offs, and so is the programming world. In


programming, we constantly balance trade-offs between code
readability, maintainability, development efficiency, and performance,
and even within each of these areas. For example, optimizing for
performance often involves trade-offs between memory savings,
execution speed, and implementation complexity.

In real-world projects, most code sections don't demand peak


performance. Prioritizing maintainability and readability generally
outweighs shaving every byte or microsecond. This book focuses on
optimizing critical sections where performance truly matters. Be
aware that some suggestions might lead to more verbose code or
only exhibit significant gains in specific scenarios.

The contents in this book include:

how to consume less CPU resources.


how to consume less memory.
how to make less memory allocations.
how to control memory allocation places.
how to reduce garbage collection pressure.
This book neither explains how to use performance analysis tools,
such as pprof, nor tries to study deeply on compiler and runtime
implementation details. The books also doesn't introduce how to use
profile-guided optimization. None of the contents provided in this
book make use of unsafe pointers and cgo. And the book doesn't
talk about algorithms. In other words, this book tries to provide
some optimization suggestions in a way which is clear and easy to
understand, for daily general Go programming.

Without particularly indicated, the code examples provided in this


book are tested and run on a notebook with the following
environment setup:

go version go1.22.1 linux/amd64


goos: linux
goarch: amd64
cpu: Intel(R) Core(TM) i5-4210U CPU @ 1.70GHz

Some benchmark times information is removed from benchmark


results, to keep benchmark lines short.

Please note that:

some of the talked suggestions in this book work on any


platform and for any CPU models, but some others might only
work on specified platforms and for specified CPU models. So
please benchmark them on the same environments as your
production environments before adopting any of them.
some implementation details of the official standard Go compiler
and runtime might change from version to version, which means
some of the talked suggestions might not work for future Go
toolchain versions.
the book will be open sourced eventually, in a chapter by
chapter way.

About the author


Tapir is the author of this book. He also wrote the Go 101 book. He
is planning to write some other Go 101 series books. Please look
forward to.

Tapir was ever (maybe will be again) an indie game developer. You
can find his games here: tapirgames.com.

About GoTV
During writing this book, the tool GoTV is used to manage
installations of multiple Go toolchain versions and check the behavior
differences between Go toolchain versions.

Feedback
Welcome to improve this book by submitting corrections to Go 101
issue list (https://github.com/go101/go101) for all kinds of mistakes,
such as typos, grammar errors, wording inaccuracies, wrong
explanations, description flaws, code bugs, etc.

It is also welcome to send your feedback to the Go 101 twitter


account: @go100and1 (https://twitter.com/go100and1).
Value Parts and Value Sizes
Values and value parts
In Go, a value of some kinds of types always contains only one part
(in memory), whereas a value of other kinds of types might contain
more than one part. If a value contains more than one part, then
one of the parts is called the direct part and the others are called
indirect parts. The direct part references the indirect parts.

If a value always contains only one part, then the part may be also
called as the direct part of the value, and we say the value has no
indirect parts.

In the official standard Go compiler implementation, each value of


the following kinds of types always contains only one part:

boolean types
numeric types (int8, uint8, int16, uint16, int32, uint32, int64,
uint64, int, uint, uintptr, float32, float64, complex64,
complex128)
pointer types
unsafe pointer types
struct types
array types
And a value of the following kinds of types always may contain one
or more indirect parts:

slice types
map types
channel types
function types
interface types
string types

When assigning/copying a value, only the direct part of the value is


copied. After copying, the direct parts of the destination and source
values both are referencing the indirect parts of the source value (if
the indirect parts exist).

At run time, each value part is carried on one memory block


(memory blocks will be explained in a following chapter). So, if a
value contains two parts, the value is very possibly distributed on
two memory blocks.

(Note: The terminology value part was invented by the Go 101


series books. It is not widely used in Go community. Personally, I
think the terminology makes some conveniences when making some
explanations.)

Value/type sizes
The size of a value part means how many bytes are needed to be
allocated in memory to store the value part at run time.

The size of a value exactly equals to the size of the direct part of the
value. In other words, the indirect parts of a value don't contribute
to the size of the value. The reason? It has been mentioned above:
when assigning/copying a value, only the direct part of the value is
copied and the indirect parts might be shared by multiple values, so
it is not a good idea to let the same indirect parts contribute to value
sizes.

In the official standard Go compiler implementation, the sizes of all


values of a specified type are all the same. So the same value size is
also called as the size of that type.

In the official standard Go compiler implementation, the sizes of all


types of the same type kind are the same, except struct and array
type kinds. From memory allocation point of view,

A struct values holds all its fields. In other words, a struct value
is composed of all its fields. At runtime, the fields of a struct are
allocated on the same memory block as the struct itself. Copying
a struct value means copying all the fields of the struct value.
So all the fields of a struct value contribute to the size of the
struct value.
Like struct values, an array value holds all its elements. In other
words, an array is composed of all its elements. At runtime, the
elements of an array are allocated on the same memory block
as the array itself. Copying an array value means copying all the
elements of the array value. So all elements of an array
contribute to the size of the array value.

A pointer doesn't hold the value being referenced (pointed) by the


pointer. So the value being referenced by the pointer value doesn't
contribute to the size of the pointer value (so nil pointers and non-nil
pointers have the same size). The two values may be often allocated
on two different memory blocks, so copying one of them will not
copy the other.

Internally, a slice uses a pointer (on the direct part) to reference all
its elements (on the indirect part). The length and capacity
information (two int values) of a slice is stored on the direct part of
the slice. From memory allocation point of view, it doesn't hold its
elements. Its elements are allocated on another (indirect) value part
other than its direct part. When assigning a slice value to another
slice value, none elements of the slice get copied. After assigning,
the source slice and the destination slice both reference (but not
hold) the same elements. So the elements of a slice don't contribute
to the size of a specified slice. This is why the sizes of all slice types
are the same.

Like slice values, a map value just references all its entries and a
buffered channel value just references its elements being buffered.

In the official standard Go compiler implementation, map values,


channel values and function values are all represented as pointers
internally. This fact is important to understand some later introduced
optimizations made by the official standard Go compiler.

In the official standard Go compiler implementation,

a string just references all its containing bytes (on the indirect
part), though in logic, we can also think a string holds all its
containing bytes. The length information of a string is stored on
the direct part of the string as an int value.
an interface value just references its dynamic value, though in
logic, we can also think an interface value holds its dynamic
value.

Detailed type sizes


The following table lists the sizes (used in the official standard Go
compiler) of all the 26 kinds of types in Go. In the table, one word
means one native word (4 bytes on 32-bit architectures and 8 bytes
on 64-bit architectures).

Type Kinds Value Size Required by Go Specification


Architecture dependent,
4 bytes on 32-bit
int, uint 1 word
architectures and 8 bytes on 64-bit
architectures.
int8, uint8
1 byte 1 byte
(byte)
Type Kinds Value Size Required by Go Specification
int16, uint16 2 bytes 2 bytes
int32 (rune),
4 bytes 4 bytes
uint32, float32
int64, uint64 8 bytes 8 bytes
float64,
8 bytes 8 bytes
complex64
complex128 16 bytes 16 bytes
Large enough to store
uintptr 1 word the uninterpreted bits
of a pointer value.
bool 1 byte Unspecified
string 2 words Unspecified
(element value
size) The size of an array type is zero if
array
* its element type has a zero size.
(array length)
pointer (safe or
1 word Unspecified
unsafe)
slice 3 words Unspecified
map 1 word Unspecified
channel 1 word Unspecified
function 1 word Unspecified
interface 2 words Unspecified
Type Kinds Value Size Required by Go Specification
(the sum of
sizes of all
The size of a struct type is zero if it
fields)
struct contains no fields that have a size
+
greater than zero.
(the number of
padding bytes)

Struct padding will be explained in the section after next.

Memory alignments
To fully utilize CPU instructions and get the best performance, the
(start) addresses of the memory blocks allocated for (the direct parts
of) values of a specified type must be aligned as multiples of an
integer N. Here N is called the alignment guarantee of that type.

For a type T , we can call unsafe.Alignof(t) to get its general


alignment guarantee of type T , where t is a non-field value of type
T, and call unsafe.Alignof(x.t) to get its field alignment guarantee
of type T , where x is a struct value and t is a field value of type T .
In the current standard Go compiler implementation, the field
alignment guarantee and the general alignment guarantee of a type
are always equal to each other.

The following table lists the alignment guarantees made by the


official standard Go compiler for all kinds of types. Again, one native
word is 4 bytes on 32-bit architectures and 8 bytes on 64-bit
architectures.

type alignment guarantee


bool, uint8, int8 1
uint16, int16 2
uint32, int32 4
float32, complex64 4
arrays depend on element types
structs depend on field types
other types size of a native word

The Go specification says:

For a variable x of a struct type: unsafe.Alignof(x) is the


largest of all the values unsafe.Alignof(x.f) for each field f of
x, but at least 1 .

For a variable x of an array type: unsafe.Alignof(x) is the


same as the alignment of a variable of the array's element type.

The official standard Go compiler ensures that the size of a type is a


multiple of the alignment guarantee of the type.

Struct padding
To satisfy type alignment guarantee rules mentioned previously, Go
compilers may pad some bytes after certain fields of struct values.
The padded bytes are counted for struct sizes. So the size of a struct
type may be not a simple sum of the sizes of all its fields.

For example, the size of the struct type shown in the following code
is 24 on 64-bit architectures.

type T1 struct {
a int8
// 7 bytes are padded here
b int64
c int16
// 6 bytes are padded here.
}

The reason why its size is 24:

The alignment guarantee of the struct type is the same as its


largest alignment guarantee of its filed types. Here is the
alignment guarantee (8, a native word) of type int64 . This
means the distance between the address of the field b and a of
a value of the struct type is a multiple of 8. Clever compilers
should choose the minimum possible value: 8. To get the
desired alignment, 7 bytes are padded after the field a .
The size of the struct type must be a multiple of the alignment
guarantee of the struct type. So considering the existence of the
field c , the minimum possible size is 24 (8x3), which should be
used by clever compilers. To get the desired size, 6 bytes are
padded after the field c .

Field orders matter in struct type size calculations. If we change the


orders of field b and c of the above struct type, then the size of the
struct will become to 16.

type T2 struct {
a int8
// 1 byte is padded here
c int16
// 4 bytes are padded here.
b int64
}

We can use the unsafe.Sizeof function to get value/type sizes. For


example:

package main

import "unsafe"

type T1 struct {
a int8
b int64
c int16
}

type T2 struct {
a int8
c int16
b int64
}

func main(){
// The printed values are got on
// 64-bit architectures.
println(unsafe.Sizeof(T1{})) // 24
println(unsafe.Sizeof(T2{})) // 16
}

We can view the padding bytes as a form of memory wasting, a


trade-off result between program performance, code readability and
memory saving.

In practice, generally, we should make related fields adjacent to get


good readability, and only order fields in the most memory saving
way when it really needs.

Value copy costs and small-size


types/values
The cost of copying a value is approximately proportional to the size
of the value. In reality, CPU caches, CPU instructions and compiler
optimizations might also affect value copy costs.

Value copying operations often happen in value assignments. More


value copying operations will be listed in the next section.

To achieve high code execution performance, if it is possible, we


should try to avoid
copying a large quantity of large-size values in a loop.
copying very-large-size arrays and structs.

Some types in Go belong to small-size types. Copying their values is


specially optimized by the official standard Go compiler so that the
copy cost is always small.

But what are small-size types? There is not a formal definition. In


fact, the definition depends on specific CPU architectures and
compiler implementations. In the official standard Go compiler
implementation, except large-size struct and array types, all other
types in Go could be viewed as small-size types.

What are small-size struct and array values? There is also not a
formal definition. The official standard Go compiler tweaks some
implementation details from version to version. However, in practice,
we can view struct types with no more than 4 native-word-size fields
and array types with no more than 4 native-word-size elements as
small-size values, such as struct{a, b, c, d int} , struct{element

*T; len int; cap int} and [4]uint .

For the official standard Go compiler 1.22 versions, a copy cost leap
happens between copying 9-element arrays and copying 10-element
arrays (the element size is one native word). The similar is for
copying 9-field structs and copying 10-field structs (each filed size is
one native word).

The proof:
package copycost

import "testing"

const N = 1024
type Element = uint64

var a9r [N][9]Element


func Benchmark_CopyArray_9_elements(b *testing.B) {
var a9s [N][9]Element
for i := 0; i < b.N; i++ {
for k := range a9s { a9r[k] = a9s[k] }
}
}

var a10r [N][10]Element


func Benchmark_CopyArray_10_elements(b *testing.B) {
var a10s [N][10]Element
for i := 0; i < b.N; i++ {
for k := range a10s { a10r[k] = a10s[k] }
}
}

type S9 struct{ a, b, c, d, e, f, g, h, i Element }


var s9r [N]S9
func Benchmark_CopyStruct_9_fields(b *testing.B) {
var s9s [N]S9
for i := 0; i < b.N; i++ {
for k := range s9s { s9r[k] = s9s[k] }
}
}

type S10 struct{ a, b, c, d, e, f, g, h, i, j Element }


var s10r [N]S10
func Benchmark_CopyStruct_10_fields(b *testing.B) {
var s10s [N]S10
for i := 0; i < b.N; i++ {
for k := range s10s { s10r[k] = s10s[k] }
}
}

The benchmark results:

Benchmark_CopyArray_9_elements-4 3974 ns/op


Benchmark_CopyArray_10_elements-4 8896 ns/op
Benchmark_CopyStruct_9_fields-4 2970 ns/op
Benchmark_CopyStruct_10_fields-4 8471 ns/op

This results indicate copying arrays with less than 10 elements and
structs with less than 10 fields might be specially optimized.

The official standard Go compiler might use different criteria for


other scenarios to determine what are small struct and array types.
For example, in the following benchmark code, the Add4 function
consumes much less CPU resources than the Add5 function (with the
official standard Go compiler 1.22 versions).

package copycost

import "testing"

type T4 struct{a, b, c, d float32}


type T5 struct{a, b, c, d, e float32}
var t4 T4
var t5 T5
//go:noinline
func Add4(x, y T4) (z T4) {
z.a = x.a + y.a
z.b = x.b + y.b
z.c = x.c + y.c
z.d = x.d + y.d
return
}

//go:noinline
func Add5(x, y T5) (z T5) {
z.a = x.a + y.a
z.b = x.b + y.b
z.c = x.c + y.c
z.d = x.d + y.d
z.e = x.e + y.e
return
}

func Benchmark_Add4(b *testing.B) {


for i := 0; i < b.N; i++ {
var x, y T4
t4 = Add4(x, y)
}
}

func Benchmark_Add5(b *testing.B) {


for i := 0; i < b.N; i++ {
var x, y T5
t5 = Add5(x, y)
}
}
The benchmark results:

Benchmark_Add4-4 2.649 ns/op


Benchmark_Add5-4 19.15 ns/op

The //go:noinline compiler directives used here are to prevent the


calls to the two function from being inlined. If the directives are
removed, the Add4 function will become even more performant.

Value copy scenarios


In Go programming, besides value assignments, there are also
several other operations involving value copying:

box non-interface values into interfaces (convert non-interface


values into interfaces).
pass parameters and return results when calling functions.
receive values from and send values to channels.
put entries into maps.
append elements to slices.
iterate container elements/entries with for-range loop code
blocks.
since Go 1.22, (implicitly) duplicate loop variables of 3-clause
for-loops.

The following are several examples which show the costs of copying
some large-size values.
Example 1:

package copycost

import "testing"

const N = 1024

//go:noinline
func Sum_RangeArray(a [N]int) (r int) {
for _, v := range a {
r += v
}
return
}

//go:noinline
func Sum_RangeArrayPtr1(a *[N]int) (r int) {
for _, v := range *a {
r += v
}
return
}

//go:noinline
func Sum_RangeArrayPtr2(a *[N]int) (r int) {
for _, v := range a {
r += v
}
return
}

//go:noinline
func Sum_RangeSlice(a []int) (r int) {
for _, v := range a {
r += v
}
return
}

//===========

var r [128]int

func buildArray() [N]int {


var a [N]int
for i := 0; i < N; i ++ {
a[i] = (N-i)&i
}
return a
}

func Benchmark_Sum_RangeArray(b *testing.B) {


var a = buildArray()
b.ResetTimer()
for i := 0; i < b.N; i++ {
r[i&127] = Sum_RangeArray(a)
}
}

func Benchmark_Sum_RangeArrayPtr1(b *testing.B) {


var a = buildArray()
b.ResetTimer()
for i := 0; i < b.N; i++ {
r[i&127] = Sum_RangeArrayPtr1(&a)
}
}

func Benchmark_Sum_RangeArrayPtr2(b *testing.B) {


var a = buildArray()
b.ResetTimer()
for i := 0; i < b.N; i++ {
r[i&127] = Sum_RangeArrayPtr2(&a)
}
}

func Benchmark_Sum_RangeSlice(b *testing.B) {


var a = buildArray()
b.ResetTimer()
for i := 0; i < b.N; i++ {
r[i&127] = Sum_RangeSlice(a[:])
}
}

The benchmark results:

Benchmark_Sum_RangeArray-4 897.6 ns/op


Benchmark_Sum_RangeArrayPtr1-4 799.3 ns/op
Benchmark_Sum_RangeArrayPtr2-4 555.3 ns/op
Benchmark_Sum_RangeSlice-4 561.7 ns/op

From the results, we could find that the Sum_RangeArray function is


the slowest one. This is not surprising, because the array value is
copied twice in calling this function. One copy happens when passing
the array as the argument (arguments are passed by copy in Go),
the other happens when ranging over the array parameter (the
direct part of the container following the range keyword will be
copied if the second iteration variable is used).

The Sum_RangeArrayPtr1 function is faster than Sum_RangeArray ,

because the array value is only copied once in calling this function.
The copy happens when range over the array.

No array copying happens in the calls to the remaining two


functions, so those two functions are both the fastest ones.

Example 2:

package copycost

import "testing"

type Element [10]int64

//go:noinline
func Sum_PlainForLoop(s []Element) (r int64) {
for i := 0; i < len(s); i++ {
r += s[i][0]
}
return
}

//go:noinline
func Sum_OneIterationVar(s []Element) (r int64) {
for i := range s {
r += s[i][0]
}
return
}

//go:noinline
func Sum_UseSecondIterationVar(s []Element) (r int64) {
for _, v := range s {
r += v[0]
}
return
}

//===================

func buildSlice() []Element {


var s = make([]Element, 1000)
for i := 0; i < len(s); i++ {
s[i] = Element{0: int64(i)}
}
return s
}

var r [128]int64

func Benchmark_PlainForLoop(b *testing.B) {


var s = buildSlice()
b.ResetTimer()
for i := 0; i < b.N; i++ {
r[i&127] = Sum_PlainForLoop(s)
}
}

func Benchmark_OneIterationVar(b *testing.B) {


var s = buildSlice()
b.ResetTimer()
Random documents with unrelated
content Scribd suggests to you:
Fig. 78.—Finished Model
CHAPTER VIII
Simple Twin-screw Biplane
According to the type formula, the machine illustrated on p. 63 is
of the 1-2-p2 type, which signifies that it has two superposed main
supporting surfaces and twin screws, and that it flies with the small
plane leading. The writer, in testing the model from which the
drawings were made, found that 300 yards were easily obtainable at
every flight at an altitude of 40 ft. or so. Although its construction is
slightly more complicated than a monoplane, this is amply
compensated for by its majestic appearance in the air.
The central spar is hollow, and measures 36 in. by ⅜ in. by ⁵/₁₆
in. Fig. 79 shows a cross-section of it. Spruce was used for this, a
groove ⅛ in. by ⁹/₃₂ in. being ploughed in the spruce, and a ³/₃₂-in.
strip being glued over the open side. The spar should be tapered off
from a point 12 in. from the front end to ¼ in. by ¼ in., to give
additional strength.
The propeller bar is mortised into one end of the central spar, and
is stayed from a point 6 in. from the rear end to ½ in. in from each
end of the propeller bar, to which projection the bearings, cut from
sheet brass, are lashed. These latter are shown in Fig. 80. To the
front of the spar are bound two hooks, formed from one continuous
length of wire. These embrace the rubber, and should be covered
with valve tubing.
Four birch struts should next be cut, ¼ in. by ¹/₁₆ in. in section
and 7 in. long, to support the main aerofoils on the spar. They should
fit over the spar in the position shown, small blocks uniting them top
and bottom, and should be so fixed that their upper ends are 3 in. off
the spar. Notches are to be cut in them 2¼ in. below the spar, to
form a convenient fixing for the lower main plane. Fig. 81 clearly
shows the struts united to one of the blocks; the saw cuts or notches
will also be apparent from this.
A cane skid is bound to the lower blocks, and to the spar at the
position indicated in Fig. 82. A half-section of a round cane is quite
suitable for this purpose.
A plan view of the model is given by Fig. 83, from which the
relative position of ribs, planes, etc., will be seen. Fig. 84 is an end
elevation looking through from the propeller end. It will be observed
that the screws rotate from approximately the centre of resistance.
The measurements given should be carefully followed to see that
this is so in the completed model. The dihedral angle of the planes
should be made 1¼ in., which is ample to provide lateral stability.
Longitudinal stability is provided by an angle of incidence on the
elevator (Fig. 85).

Fig. 79 Fig. 80 Fig. 81


Fig. 82

Fig. 83
Fig. 84

Fig. 85

Figs. 79 to 85.—General Arrangement and Details of


Biplane
The elevator is constructed from one continuous length of No. 18
gauge piano wire. It is rectangular in plan, the joint being a soldered
one at the centre of the trailing edge. The centre rib projects
downwards for 1½ in., which projection fits into a hole bored in the
nose of the machine with just sufficient friction to retain it in place. It
should also be bent back at an angle to cause the trailing edge to
bind on the spar sufficiently to allow it to swivel in the event of it
striking any fixed object. From this centre rib the elevation of the
complete machine is adjusted. The main planes are constructed
from birch ¼ in. by ¹/₁₆ in. in cross-section, five ribs connecting the
longer spars. No camber is given to the ribs; they should be cut off 1
in. or no longer than is necessary, pinned to the spars, so that the
latter are 5 in. apart, and cut off flush after the glue is dry. The top
plane, it will be noticed, has an overhang of 3 in. The planes are
covered underneath to eliminate the undulations which would
otherwise be caused by the ribs. Fabric should be sewn to the
elevator frame.
The top plane is lashed to the struts in the manner shown in Fig.
86, the centre rib resting on the small wooden blocks, while the
bottom plane is sprung into the notches previously referred to. Four
No. 20 s.w.g. wire stanchions, with eyes bent in them top and
bottom, as shown in Fig. 87, will next be required to form an
anchorage for the wing bracing, and to maintain the “gap” at the tips
of the wings. Brass wire will do for them, and when made their ends
should be forced through the spars in the position shown in the end
elevation, and then clinched over.
Bracing the wings should next be undertaken, and carpet thread
should be requisitioned for this purpose. It is the easiest matter
possible to warp the wings in this operation, so that too much care
cannot be taken in this respect. It should be understood that the
bracing is fixed to holes in the wooden stanchions, where it must be
securely tied, and not continued to the opposite side without, or the
wings will rock laterally, and so cause instability. Sufficient tension
should be placed on the threads which pass to the wing tips of the
bottom plane to impart a 1¼-in. dihedral angle.
Fig. 86
Fig. 87

Fig. 88

Figs. 86 to 88.—Details of Biplane


The last, and perhaps the most important, unit of the model
should be made—the propellers. Cut a pair of blanks, as shown in
Fig. 88, to shape from ¹/₁₆-in. birch to form the propellers. Strips of
tinfoil are wrapped round their centres, to which the spindles are
soldered. Bend the blades at the dotted lines under a jet of steam
from a kettle, making them to revolve in opposite directions. They
rotate on steel-cupped washers placed on the spindles.
Motive power is supplied from eight strands per side of ¼-in. strip
rubber well lubricated with soft soap emulsified with water. These
skeins will stand 650 turns each, which number should be gradually
worked up to on new rubber, and not applied at the first flight. A
perspective view of the finished model is given in Fig. 89.
Flying the Model.—Having selected a large open space clear of
trees, give about 100 turns on the propellers in order to adjust the
elevator. If the model points its nose in the air it is elevated too
much. If the model flies too low it is not elevated enough. In each
case it requires adjustment until the precise position is arrived at.
The elevator should never be at a greater angle than 8° or less than
5°. If the machine still flies too low with the elevator at 5° the main
planes will have to be moved forward slightly; but the exact position
is only found by experiment. If it flies too high with the elevator at 5°
the planes will have to be put back.
Fig. 89.—The Finished Biplane
Much depends on the way a model is launched. The proper way
is to hold it by the propellers, with the thumb and forefinger along the
main sticks, taking care not to bend the propeller hooks; then hold at
about the angle shown in the photograph (Fig. 90). and launch as
near as possible with the wind.
Fig. 90.—Launching the Model
It is necessary when flying in windy weather to launch the
machine high and smartly, as the wind has a tendency to beat it to
the ground. Both propellers must be released at exactly the same
time. It should be carefully watched while it is flying, and if it persists
in turning, say to the right, the fault will probably be that the left
propeller is more effective, or the planes on the left side of the
machine are elevated more. For straight flights it is most important
that all the planes should be in perfect alignment; but to succeed in
making a twin-propelled model fly perfectly straight is largely a
matter of perfect construction.
The model can be steered by means of the elevator (looking at
the machine from the propeller end); if the elevator is lowered on the
left side it will fly round to the left, and vice versa. The adjustment
necessary to effect this can be accomplished by means of the slot in
one of the elevator uprights.
CHAPTER IX
Winders for Elastic Motors
To a model aeroplane enthusiast a winder is an enormous
acquisition. The converted egg-beater type of winder, so much in
evidence, leaves much to be desired, the chief fault being that the
bearing spindles wear so quickly, apart from the fact that they are
awkward to manipulate single-handed. A second person is generally
required to support the model.
The winder here illustrated bears the distinct advantage that one
person can wind, keep the model in alignment with, and forced into,
the chuck simultaneously. The construction and general details will
be fairly obvious from the accompanying illustrations, so that it will
only be necessary to give a brief description.
It consists of an ash stump, 13 in. by 1 in. by 1 in., tapered at one
end, as in Fig. 91, to facilitate its being forced into the ground. A gear
and pinion (see Fig. 91), which may be requisitioned from some of
the cheaper type of clockworks, are mounted at the top end of the
stump in a casting of No. 18 s.w.g. brass, which is secured to the
ash by means of two round-headed screws (see Fig. 94). It will be
found that for general purposes a gear ratio of six to one will be most
suitable. Thus the pinion may have ten teeth and the gear sixty. The
handle should be bent to shape after being passed through the
stump. Copper ferrules are used on the spindles to keep the gears
central between the casing, as shown in Fig. 93, and should allow a
little play to ensure easy rotation. The pinion spindle must be
flattened out after the gearing is put together, and the hardwood
chuck then driven on. A glance at Fig. 92 will show clearly what is
meant. The slot in the chuck should be made sufficiently large to
take a carved propeller.
Fig. 91 Fig. 92
Fig. 93 Fig. 94

Figs. 91 to 94.—A Model Aeroplane Winder


Fig. 94A.—The Winder

Fig. 95.—An Egg Whisk


Fig. 95A.—Using Twin Winder
A Double Winder.—As each propeller of a twin-screw machine
requires to be wound up 400 to 500 times, it is obviously necessary
to use a geared-up winder. This can easily be constructed out of an
ordinary egg-beater, and one converted into a very useful instrument
is shown by Fig. 94A. The great advantage of using a winder of this
type is that both propellers can be wound simultaneously. Figs. 95
and 95A clearly show how the alteration is made; it is quite simple,
and all the tools required are a three-cornered file, a drill, and a
soldering bit. The egg-beater can be obtained for a few pence at any
ironmonger’s. The two hooks at the nose of the machine are
attached to the cross-pieces on the winder, and the rubber is wound
in the same direction as the propellers revolve (see Fig. 95A). The
winder shown is geared 5 to 1, so that 100 turns on the winder gives
500 turns on the propellers. Geared-up winders may be purchased
fairly cheap.
CHAPTER X
Collapsible Monoplane
The difficulty of carrying a fairly large model to a convenient flying
ground prevents many would-be makers taking a practical interest in
model flying. The necessity of overcoming this difficulty has resulted
in several excellent designs, one of the best being the monoplane
designed and constructed by Mr. A. B. Clark, the secretary of the
South-Eastern Model Aero Club. When this model monoplane was
built the objects aimed at were extreme reliability and easy
conveyance to and from the flying grounds situated some distance
away.
The model is fitted with a chassis to enable it to start off good
ground under its own power; but this starting-gear is so constructed
that the whole model will pack up flat and make a convenient parcel.
In fact, the complete model will easily go into a cardboard box
measuring 2 ft. 10 in. by 1 ft. 2 in.
Referring to the accompanying illustrations, Fig. 96 shows the
plan view of the complete machine, Fig. 97 a side view, and Fig. 98 a
front view. The body (fuselage) is made of two pieces of silver
spruce, 3 ft. 6 in. long, ⅜ in. deep, and ³/₁₆ in. thick. These gradually
taper towards each end, where they measure ¼ in. by ³/₁₆ in. Two
distance pieces of bamboo are shaped to streamline form and
placed at equal distances along the fuselage; the front piece is 2⅜
in., the other 2 in. These pieces should be pointed at the ends, and
fit in a slot made in the side lengths, as indicated at A (Fig. 99), and
then bound very tightly with glued narrow silk tape or ribbon, as
indicated at B. This is the neatest and also the strongest method of
making joints for model aeroplane frames. The ends of the two long
lengths should be bound together with strong thread and carefully
glued.
The tail (used in place of the familiar elevator) is built on to the
rear end of the fuselage, and is composed of two pieces of yellow
bamboo, 9¾ in. by ⁵/₁₆ in by ⅛ in., tapering to ⅛ in. square at the
end to which the propeller bearings are attached. These bearings
are made of No. 18 or No. 20 s.w.g. piano wire, and their shape is
clearly shown in Fig. 100. The bearings are bound to the inner edge
of the wood with glued thread or fine flower wire. The wide ends of
the bamboo lengths are held over a bunsen (the blue flame of an
incandescent burner is very suitable), and bent to the angle shown in
Fig. 96. The trailing edge of the tail is made of No. 26 s.w.g. piano
wire, or a G banjo string. The wire is taken right through the end of
the fuselage, a small hole being carefully drilled ½ in. from the end.
A bead of solder should be run on the wire on both sides of the hole,
to prevent movement in a lateral direction, and the two ends are
taken through the bearings and bound to the bamboo with fine wire,
leaving sufficient to form hooks for the two bracing wires to be
afterwards attached. The whole of this tail framework is covered with
proofed silk on both sides, thus forming an approximate streamline
surface, which has proved remarkably efficient. Two triangular
pieces of silk should be cut out, just large enough to give sufficient
overlap. They should be attached with fish glue, and stretched as
tightly as possible.
Fig. 96

Fig. 97
Fig. 98
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!

textbookfull.com

You might also like