В сей заметке речь пойдет о языке Go, с которым я имел удовольствие познакомиться несколько недель назад. Будут освещены особенности языка и разобрана небольшая программка на нем. Напоследок я поделюсь своими субъективными впечатлениями от работы с Go.

Изучаем Go «за 24 часа»

Если вам хочется ознакомиться с синтаксисом языка Go, настоятельно рекомендую прочитать Go by Example . Отличительные черты языка:

  • Строгая статическая типизация (утиная типизация в случае интерфейсов);
  • Полноценная поддержка юникода;
  • Java/Python/Haskell-подобный сборщик мусора;
  • Прочие фишки функционального программирования — лямбды, замыкания и тп;
  • В Go есть указатели, но над ними нельзя выполнять арифметические операции, как в C/C++/D;
  • Так называемые goroutines — легковесные потоки, для управления которыми и взаимодействия между которыми в Go предусмотрены удобные средства;
  • Отсутствие ООП-фанатизма, по большому счету Go является процедурным языком с поддержкой интерфейсов;
  • Нет поддержки исключений, вместо них предлагается использовать интерфейс error и возможность функций возвращать несколько значений;
  • Язык является интерпретируемым и компилируемым, при разработке Go особое внимание уделялось скорости компиляции;
  • Разработан в Google, проектированием занимались Кен Томпсон, Роб Пайк и Роберт Гризмер, если эти имена вам о чем-то говорят;

Установка Go под Debian:

sudo apt-get install golang

Под FreeBSD существует бинарный пакет, но он довольно старый. Устанавливаться из портов Go отказался, дескать «go-1.0.1,1 is only for amd64, while you are running i386» 0_o. Однако установка в соответствии с инструкцией на сайте проекта прошла без проблем.

Запуск интерпретатора:

go run test.go

Компиляция:

go build test.go

В качестве IDE я использовал свой любимый VIM . Подсветка синтаксиса настраивается так:

mkdir -p ~ / .vim / syntax /
cd ~ / .vim / syntax /
wget http: // go.googlecode.com / hg-history / release / misc / vim / syntax / go.vim
mkdir -p .. / ftdetect /
echo ‘au BufRead,BufNewFile *.go set filetype=go’ > .. / ftdetect / go.vim

Вроде, все. Как там обстоят дела под Windows — не ведаю.

Теперь напишем немного кода

Рассмотрим задачу из программерского конкурса от Darkus’а за июнь . Как правило, при изучении нового языка программирования, сначала я пишу прогу на уже знакомом мне языке, в роли которого обычно выступает Perl :

#!/usr/bin/perl

use strict ;
use warnings ;

my %cubes ;
my $min_dist = 999 _999_999 ;
my @min_pair = ( undef , undef ) ;
my $infile = shift ;

my $nbits = 9 ;

open my $fid , $infile or die $! ;
my $nline = 0 ;
while ( < $fid > ) {
s/(^s+|s+$)//gs ;
my ( $x , $y , $z ) = split /s+/ ;
my @compare = ( ) ;
my ( $cx , $cy , $cz ) = ( ( $x >> $nbits ) ,
( $y >> $nbits ) ,
( $z >> $nbits ) ) ;
for my $dx ( ( 1 , 0 , 1 ) ) {
for my $dy ( ( 1 , 0 , 1 ) ) {
for my $dz ( ( 1 , 0 , 1 ) ) {
my $ncube = ( $cx + $dx ) . ‘.’ . ( $cy + $dy ) . ‘.’ . ( $cz + $dz ) ;
next unless defined $cubes { $ncube } ;
push @compare , $_ for ( @ { $cubes { $ncube } } ) ;
}
}
}

for my $curr ( @compare ) {
my $dist = ( $x $curr -> [ 0 ] ) ** 2 +
( $y $curr -> [ 1 ] ) ** 2 +
( $z $curr -> [ 2 ] ) ** 2 ;
next unless $dist > 0 ;
if ( $dist < $min_dist ) {
$min_dist = $dist ;
@min_pair = ( [ $x , $y , $z ] , $curr ) ;
}
}

push @ { $cubes { $cx . ‘.’ . $cy . ‘.’ . $cz } } , [ $x , $y , $z ] ;

$nline ++;
if ( $nline % 10000 == 0 ) {
warn «parsed $nline lines, min_dist**2 = $min_dist n » ;
}
}
close $fid ;
print «DONE! MIN_DIST = » . sqrt ( $min_dist ) . » n » ;
print «($min_pair[0][0],$min_pair[0][1],$min_pair[0][2]) n » ;
print «($min_pair[1][0],$min_pair[1][1],$min_pair[1][2]) n » ;

… а затем переписываю ее на изучаемый язык:

package main

import (
«github.com/glenn-brown/golang-pkg-pcre/src/pkg/pcre»
«container/list»
«strconv»
«bufio»
«math»
«fmt»
«os»
)

type Point3D struct {
x , y , z int
}

const nbits = 9

func pointToKey ( point Point3D ) Point3D {
return Point3D {
point . x >> nbits ,
point . y >> nbits ,
point . z >> nbits ,
}
}

type PointReader pcre . Regexp

func newPointReader () ( pr PointReader ) {
re := pcre . MustCompile ( «( \ d+) \ s+( \ d+) \ s+( \ d+)» , 0 )
return PointReader ( re )
}

func ( pr PointReader ) readPoint ( str string ) (
rslt Point3D , err error ) {
m := pcre . Regexp ( pr ) . MatcherString ( str , 0 )
if ! m . Matches () {
err = fmt . Errorf ( «Failed to parse ‘%v’ n » , str )
return
}

x , _ := strconv . Atoi ( string ( m . Group ( 1 )))
y , _ := strconv . Atoi ( string ( m . Group ( 2 )))
z , _ := strconv . Atoi ( string ( m . Group ( 3 )))
rslt = Point3D { x , y , z }
return
}

func pow2 ( x int ) int {
return x * x ;
}

func main () {
if len ( os . Args ) < 2 {
fmt . Fprintf ( os . Stderr , «Usage: n %v [infile] n » , os . Args [ 0 ])
return
}

fid , ferr := os . Open ( os . Args [ 1 ])
if ferr != nil {
return
}

cubes := make ( map [ Point3D ] * list. List );
reader := bufio . NewReader ( fid );
minDist := 999999999
minPair := make ([] Point3D , 2 )
nline := 0 ;
pr := newPointReader ()
for {
line , rerr := reader . ReadString ( n )
if rerr != nil { break }

point , perr := pr . readPoint ( line )
if perr != nil {
fid . Close ()
return
}

compare := list . New ()
initKey := pointToKey ( point )
for dx := 1 ; dx < = 1 ; dx ++ {
for dy := 1 ; dy < = 1 ; dy ++ {
for dz := 1 ; dz < = 1 ; dz ++ {
key := initKey
key . x += dx
key . y += dy
key . z += dz
values , exists := cubes [ key ]
if ! exists { continue }
compare . PushBackList ( values )
}
}
}

for curr := compare . Front (); curr != nil ; curr = curr . Next (){
currPoint , _ := curr . Value . ( Point3D )
dist := pow2 ( point . x currPoint . x ) +
pow2 ( point . y currPoint . y ) +
pow2 ( point . z currPoint . z )
if dist == 0 { continue }
if dist < minDist {
minPair [ 0 ] = point
minPair [ 1 ] = currPoint
minDist = dist
}
}

value , exists := cubes [ initKey ]
if ! exists {
value = list . New ()
cubes [ initKey ] = value
}
value . PushBack ( point )

nline ++ ;
if nline % 10000 == 0 {
fmt . Printf ( «Parsed %v lines, minDist**2 = %v n » ,
nline , minDist )
}
}
fid . Close ()

fmt . Printf ( «MIN_DIST = %v, minPair = %v n » ,
math . Sqrt ( float64 ( minDist )), minPair );
}

Думается, в общих чертах текст данной программы должен быть ясен любому более-менее опытному программисту. Обратите внимание на то, как в Go происходит обработка ошибок, выделение памяти, преобразование типов и проверка на существование элемента в ассоциативном массиве с заданным ключом. Однако здесь использованы далеко не все особенности Go. Как минимум, за кадром остались интерфейсы, goroutines и лямбды, но эти и другие вопросы я вынужден оставить вам для самостоятельного изучения.

Вы, конечно же, не могли не обратить внимание на использование в данной программе библиотеки PCRE . Разумеется, программа во время своей работы ничего не скачивает с гитхаба, просто таким образом в Go разрешаются конфликты в именах библиотек. Установка упомянутой библиотеки под Debian производится следующим образом:

sudo apt-get install libpcre++-dev
sudo go get github.com / glenn-brown / golang-pkg-pcre / src / pkg / pcre

Я был вынужден использовать PCRE, потому что стандартная библиотека Go для работы с регулярными выражениями полностью написана на самом Go. В связи с этим, ее производительность не то, чтобы ни на что не годилась, но все же заметно уступает PCRE.

Мой первый вариант программы на Go решал поставленную задачу за 225 секунд, используя 315 Мб оперативной памяти, причем Perl-скрипту требовалось всего лишь 100 секунд и на каких-то 50 Мб больше оперативки. Поначалу я был очень расстроен. Но разобравшись в проблеме и произведя соответствующие оптимизации (использование PCRE, кэширование скомпилированных регулярных выражений и некоторые другие), я сотворил программу, которую привел выше. На моем средненьком, по нынешним меркам, компьютере она справляется с задачей за 28 секунд и использует 177 Мб оперативной памяти.

Существуют и другие свидетельства явного превосходства Go в плане производительности не только над интерпретируемыми, но и некоторыми компилируемыми языками программирования:

Что характерно, во время работы программы происходит несколько очень коротких, но все же заметных невооруженным взглядом подвисаний. Скорее всего, во время этих подвисаний запускается сборщик мусора, который блокирует выполнение всех потоков.

Дополнение: По состоянию на 2015-ый год язык Go очень здорово разогнали, теперь по скорости он сравним с Java и Haskell. Кроме того, в нем больше не возникает упомянутых выше «подвисаний».

Мой вердикт следующий…

После многолетнего опыта работы с Perl, язык Go кажется мне слишком многословным. Сравните приведенные выше исходники или попробуйте написать на Go аналог следующего кода:

print «$_ => $hash{$_} n »
for sort { $hash { $a } <=> $hash { $b } } keys %hash ;

Но во всем остальном он очень даже неплох. Go прост в изучении и имеет широкую область применения. Язык представляет собой неплохой компромисс между выразительностью скриптовых и производительностью компилируемых языков. Хотя баланс чуть сильнее сдвинут в сторону Си, чем я ожидал.

На данный момент уже был выпущен Go 1.0, в котором окончательно зафиксировали синтаксис языка. На сайте проекта можно найти много годной документации и большую коллекцию готовых библиотек . В Google Groups было найдено активное сообщество программистов .

Особенно меня порадовало, что Go не позволяет скомпилировать программу, если в ней имеются неиспользуемые переменные или модули. Такая простая и полезная вещь, но, кажется, до этого она мне еще нигде не встречалась.

А вы уже пробовали писать на Go? Если да, то каковы ваши впечатления от этого языка? Если нет, то планируете ли попробовать?

Дополнение: Также вас могут заинтересовать посты Некоторые подводные грабли в языке Go и Некоторые тонкости управления зависимостями в Go .

admin

Share
Published by
admin

Recent Posts

Консоль удаленного рабочего стола(rdp console)

Клиент удаленного рабочего стола (rdp) предоставляет нам возможность войти на сервер терминалов через консоль. Что…

1 месяц ago

Настройка сети в VMware Workstation

В VMware Workstation есть несколько способов настройки сети гостевой машины: 1) Bridged networking 2) Network…

1 месяц ago

Логи брандмауэра Windows

Встроенный брандмауэр Windows может не только остановить нежелательный трафик на вашем пороге, но и может…

1 месяц ago

Правильный способ отключения IPv6

Вопреки распространенному мнению, отключить IPv6 в Windows Vista и Server 2008 это не просто снять…

1 месяц ago

Ключи реестра Windows, отвечающие за параметры экранной заставки

Параметры экранной заставки для текущего пользователя можно править из системного реестра, для чего: Запустите редактор…

1 месяц ago

Как управлять журналами событий из командной строки

В этой статье расскажу про возможность просмотра журналов событий из командной строки. Эти возможности можно…

1 месяц ago