🚀 Не уверены, стоит ли переходить на зрелую ИИ-инженерию? Начните с демо-урока!
Вот-вот стартует наш курс AgentOps. Если вы сомневаетесь в формате, просто оставьте заявку и получите бесплатный демо-урок «AI-инструменты в разработке: как писать код быстрее с помощью ассистентов».
Для тех, кто готов мощно прокачать портфолио, прямо сейчас действует предложение «3 любых курса по цене 1»:
👉 Получить демо-урок и зафиксировать спецпредложение 3 в 1
Вот-вот стартует наш курс AgentOps. Если вы сомневаетесь в формате, просто оставьте заявку и получите бесплатный демо-урок «AI-инструменты в разработке: как писать код быстрее с помощью ассистентов».
Для тех, кто готов мощно прокачать портфолио, прямо сейчас действует предложение «3 любых курса по цене 1»:
— При покупке VIP-тарифа (осталось 4 места) нового потока «ИИ-агенты» вы получаете в подарок доступ к курсу «AgentOps» + ещё один любой курс Академии на выбор
— В деньгах это два топовых курса по автоматизации и контролю ИИ всего за 134.000 ₽ вместо 263.000 ₽ 🔥 А за счет третьего курса (например, можно выбрать «Математику») вы соберете мощный стек и освоите целое востребованное направление.
— Платеж можно разбить на несколько частей с помощью беспроцентной рассрочки.
👉 Получить демо-урок и зафиксировать спецпредложение 3 в 1
👍1
os.ReadFile() работает с памятьюВо многих туториалах по Go встречается фраза «
os.ReadFile() читает весь файл в память». Мы привыкли принимать такие утверждения на веру, хотя проверить их легко. В этом посте разберём, что на самом деле происходит с памятью процесса, когда
os.ReadFile() обрабатывает файл на 500 мегабайт, и почему результат измерений может удивить.Берём простую программу. Она читает файл, путь к которому передан аргументом, выводит его размер и затем ждёт нажатия Enter, чтобы у нас было время посмотреть на потребление памяти процесса:
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Println("Please specify a path")
return
}
b, err := os.ReadFile(os.Args[1])
if err != nil {
panic(err)
}
fmt.Println("File has been read into memory.")
fmt.Println("Size:", len(b), "bytes")
fmt.Println("Press Enter to exit...")
fmt.Scanln()
}
Сначала создаём файл на 500 мегабайт:
fallocate -l 500M bigfile.txt
Запускаем программу через
go run:go run readingFile_bad.go bigfile.txt
Пока программа стоит на паузе, смотрим на её RSS, то есть на объём физической памяти, реально занятой процессом:
ps -o pid,rss,vsz,cmd -p 228159
PID RSS VSZ CMD
228159 3116 1750908 /tmp/go-build2199976311/b001/exe/readingFile_bad bigfile.txt
RSS показывает примерно 3 мегабайта, хотя файл весит 500. Логично ожидать, что вся эта память должна быть занята. Разберёмся, почему этого не происходит.
Когда
os.ReadFile() вызывается, Go выделяет на куче backing array размером с файл, в нашем случае 500 мегабайт, и операционная система копирует туда содержимое файла с диска. Переменная
b []byte, которую мы получаем, это не сами данные. Это всего лишь заголовок слайса, маленькая структура размером около 24 байт на 64-битной системе, которая хранит три поля: указатель на первый байт backing array, текущую длину слайса, и полную ёмкость backing array.Сам слайс маленький, а массив с данными файла занимает реальные 500 мегабайт где-то в куче:
b │
├── pointer ───────────────┐
├── length = 524288000 |
└── capacity = 524288000 │
▼
+-----------------------+
| 500 MB backing array |
+-----------------------+
Дело в том, что в нашей программе после
fmt.Scanln() переменная b больше нигде не используется. Компилятор и сборщик мусора видят, что b мертва после этой точки, поэтому backing array становится недоступным для использования и сборщик мусора может его освободить ещё до того, как мы посмотрели на RSS.Чтобы проверить эту гипотезу, раскомментируем строку с обращением к
b[0] в конце программы:fmt.Println("FirstByte: ", b[0])Теперь компилятор знает, что переменная понадобится после
Scanln(), и не может позволить сборщику мусора забрать её раньше времени. Запускаем программу заново и смотрим на RSS:ps -o pid,rss,vsz,cmd -p 233685
PID RSS VSZ CMD
233685 514680 1750844 /tmp/go-build1663734078/b001/exe/readingFile_bad bigfile.txt
На этот раз RSS равен 514680 килобайт, то есть около 514 мегабайт. Это уже соответствует ожиданиям.
Стоит добавить ещё один нюанс. Команда
go run сама по себе не запускает наш код напрямую, она сначала компилирует временный исполняемый файл во временную директорию и уже его запускает отдельным процессом. Поэтому при анализе памяти нужно смотреть не на процесс go run, а на дочерний процесс из /tmp/go-build.../exe/, именно он реально читает файл.os.ReadFile() действительно выделяет память под весь файл целиком, утверждение из туториалов верное. Но []byte, который мы получаем, это лёгкий заголовок слайса, а не сами данные, и поведение сборщика мусора влияет на то, когда именно эта память физически занята процессом. Если переменная с данными становится недостижимой раньше, чем вы успели посмотреть на RSS, может показаться, что файл вообще не загружался в память, хотя на деле это просто сборка мусора отработала раньше, чем мы ожидали.Полезный практический вывод из этого расследования простой. Если вам нужно работать с большими файлами, лучше избегать
os.ReadFile() и использовать потоковое чтение через bufio.Reader или io.Copy, чтобы не держать весь файл в памяти разом.📍 Навигация: Вакансии • Задачи • Собесы
#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤4🔥1
⚠️ Уже завтра стартует курс AgentOps!
Мы собрали на потоке сборную из мастеров IT-рынка. Практики из BigTech научат вас контролировать и отлаживать ИИ-агентов, чтобы они работали предсказуемо и не сливали бюджет на API.
🔥 Заберите 3 курса по цене 1:
Хотите прокачать свое портфолио продакшн-кейсом, но пока сомневаетесь? Пройдите наш бесплатный демо-урок, чтобы протестировать формат перед покупкой.
👉 Забрать 3 курса по цене 1 и получить демо-урок
Мы собрали на потоке сборную из мастеров IT-рынка. Практики из BigTech научат вас контролировать и отлаживать ИИ-агентов, чтобы они работали предсказуемо и не сливали бюджет на API.
🔥 Заберите 3 курса по цене 1:
● При покупке VIP-тарифа (осталось 4 места) нового потока «Разработка ИИ-агентов» получаете в подарок курс «AgentOps» + ещё один любой курс Академии (например, «Математика для разработки AI», чтобы глубже освоить направление).
● Три курса обойдутся вам всего в 134.000 ₽ вместо 263.000 ₽.
● Доступна удобная беспроцентная рассрочка, платеж можно разбить на несколько комфортных частей.
Хотите прокачать свое портфолио продакшн-кейсом, но пока сомневаетесь? Пройдите наш бесплатный демо-урок, чтобы протестировать формат перед покупкой.
👉 Забрать 3 курса по цене 1 и получить демо-урок
❤1
В сабреддите r/golang разгорелось обсуждение, которое касается почти каждого, кто пишет на Go. Автор треда обратил внимание, что в 1.26 основная работа снова шла не в синтаксис, а в рантайм, тулчейн, планировщик и сборщик мусора.
Дальше встал вопрос, стоит ли языку и дальше расти медленно, вкладываясь в производительность и инструменты, или пора добавлять больше фич, и было ли многолетнее сдерживание силой языка или тормозом для его развития.
Самая частая тема в треде это обратная совместимость. Многие участники прямо пишут, что выбрали Go именно потому, что код, написанный десять лет назад, до сих пор компилируется и работает так же, как раньше.
Вторая крупная тема это размер языка. Часть комментаторов прямо говорит, что любит Go именно за то, что в нём мало способов сделать одно и то же. Чем меньше в языке конструкций, тем меньше поводов для споров в код-ревью и тем легче читать чужой код, написанный по совершенно другим командам и в другое время.
Почти никто в треде не просит радикальных изменений синтаксиса, но запрос на улучшения всё же есть. Чаще всего звучат три темы:
• Производительность и сборщик мусора, который многие сравнивают не в пользу Go с JVM.
• Полноценные enum, которые называют одной из немногих фич, добавление которых не выглядит избыточным.
• Доработка инструментов, в частности
encoding/json/v2, который уже называют заметным шагом вперёд.💬 Что вы думаете? Стоит ускорять разработку го и было ли многолетнее сдерживание силой языка или тормозом для его развития?
📍 Навигация: Вакансии • Задачи • Собесы
#GoTalk
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
🧑💻 Сравнение двух Go-структур без хардкода полей. Часть 3, как это устроено внутри
В прошлой части мы договорились про тег
Сначала разворачиваем указатели и проверяем, что перед нами действительно структуры.
Функция принимает
Дальше один раз проходим по модели базы и строим карту вида имя колонки в значение поля. Так матчинг работает за O(1) на поле, а порядок полей в структурах может не совпадать:
Имя колонки достаётся из тега GORM, который бывает грязным, например
Потом идём по полям DTO. Поле без тега
Поле, чей тег указывает на несуществующую колонку, тоже пропускаем без паники:
Каркас готов. Осталось самое скользкое, сравнение значений с указателями и nil. Об этом в следующей части.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoDeep
В прошлой части мы договорились про тег
match. Теперь посмотрим на реализацию. Она занимает около 130 строк и состоит из обычной рефлексии.Сначала разворачиваем указатели и проверяем, что перед нами действительно структуры.
Функция принимает
any с обеих сторон, поэтому работает с любым типом:func (m *Mapper) CompareAndMap(dto any, db any) (map[string]any, error) {
dtoVal := reflect.ValueOf(dto)
dbVal := reflect.ValueOf(db)
if dtoVal.Kind() == reflect.Ptr {
dtoVal = dtoVal.Elem()
}
if dbVal.Kind() == reflect.Ptr {
dbVal = dbVal.Elem()
}
if dtoVal.Kind() != reflect.Struct || dbVal.Kind() != reflect.Struct {
return nil, fmt.Errorf("both dto and db must be structs or pointers to structs")
}
// ...
}Дальше один раз проходим по модели базы и строим карту вида имя колонки в значение поля. Так матчинг работает за O(1) на поле, а порядок полей в структурах может не совпадать:
func (m *Mapper) buildColumnMap(dbVal reflect.Value) map[string]reflect.Value {
columnMap := make(map[string]reflect.Value)
dbType := dbVal.Type()
for i := 0; i < dbType.NumField(); i++ {
if column := m.parseGormColumn(dbType.Field(i).Tag.Get(dbGormTag)); column != "" {
columnMap[column] = dbVal.Field(i)
}
}
return columnMap
}Имя колонки достаётся из тега GORM, который бывает грязным, например
type:smallint;column:is_suspended. Режем по точке с запятой и берём кусок после column:func (m *Mapper) parseGormColumn(gormTag string) string {
for _, part := range strings.Split(gormTag, ";") {
if col, ok := strings.CutPrefix(part, dbColumnKey+":"); ok {
return col
}
}
return ""
}Потом идём по полям DTO. Поле без тега
match пропускаем, так DTO может иметь поля, которые мы намеренно не синхронизируем.Поле, чей тег указывает на несуществующую колонку, тоже пропускаем без паники:
for i := 0; i < dtoVal.NumField(); i++ {
dtoField := dtoVal.Field(i)
dtoFieldType := dtoType.Field(i)
dbGormColumnName := dtoFieldType.Tag.Get(dtoMatchTag)
if dbGormColumnName == "" {
continue // нет тега match, это не наша забота
}
dbField, ok := columnMap[dbGormColumnName]
if !ok {
continue // DTO ссылается на колонку, которой нет в модели базы
}
// ... сравниваем dtoField и dbField
}Каркас готов. Осталось самое скользкое, сравнение значений с указателями и nil. Об этом в следующей части.
📍 Навигация: Вакансии • Задачи • Собесы
#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2❤1
Go Developer — удаленно по Москве
Middle Golang разработчик — от 320 000 ₽, удаленно
Senior Golang Developer — до 500 000 ₽, гибрид в Москве или удаленно
#GoWork
Please open Telegram to view this post
VIEW IN TELEGRAM
В прошлой части мы дошли до момента, где надо сравнить два поля. На этом месте чаще всего ломаются наивные реализации. Реальные модели базы полны
*string, *int64, *time.Time. Указатель может быть nil, то есть «значения нет». Сравнение указателя с не-указателем, или двух указателей, где один nil, требует аккуратности:
dtoIsPtr := dtoField.Kind() == reflect.Ptr
dbIsPtr := dbField.Kind() == reflect.Ptr
if dtoIsPtr && dbIsPtr {
if dtoField.IsNil() && dbField.IsNil() {
continue // оба nil, ничего не изменилось
}
if dtoField.IsNil() != dbField.IsNil() {
// одна сторона nil, другая нет, это точно изменение
if dtoField.IsNil() {
updateMap[dbGormColumnName] = nil
} else {
updateMap[dbGormColumnName] = dtoField.Elem().Interface()
}
continue
}
// оба не nil, разворачиваем и сравниваем значения
dtoField = dtoField.Elem()
dbField = dbField.Elem()
} else if dtoIsPtr || dbIsPtr {
// смешанный случай, одна сторона указатель, другая обычное значение
if dtoIsPtr {
if dtoField.IsNil() {
updateMap[dbGormColumnName] = nil
continue
}
dtoField = dtoField.Elem()
}
if dbIsPtr {
if dbField.IsNil() {
updateMap[dbGormColumnName] = dtoField.Interface()
continue
}
dbField = dbField.Elem()
}
}
Кода много, но каждая ветка отвечает на один вопрос. Оба nil, делать нечего. Один из двух nil, наличие значения изменилось, пишем
nil или новое значение. Оба заданы, разворачиваем и сравниваем как обычные значения. Смешанный случай, разворачиваем ту сторону, что указатель. Ошибка тут даёт либо лишние обновления, либо пропущенные, и оба бага видны только в проде.Сравнение значений
Когда обе стороны развёрнуты до обычных значений, остаётся сравнить. Ожидаешь однострочник
a.Interface() == b.Interface(), но есть ловушка. Одно и то же логическое число может храниться разными типами. В DTO поле CityID int, а в модели базы CityID int64. Для оператора == в Go это разные типы, и сравнение их упакованных форм вернёт false. Поле будет помечаться как изменённое при каждой синхронизации. Поэтому
compareValues приводит числовые семейства к общему виду:func (m *Mapper) compareValues(a, b reflect.Value) bool {
aKind := a.Kind()
bKind := b.Kind()
switch {
case m.isSignedInt(aKind) && m.isSignedInt(bKind):
return a.Int() == b.Int()
case m.isUnsignedInt(aKind) && m.isUnsignedInt(bKind):
return a.Uint() == b.Uint()
case m.isSignedInt(aKind) && m.isUnsignedInt(bKind):
return a.Int() >= 0 && uint64(a.Int()) == b.Uint()
case m.isUnsignedInt(aKind) && m.isSignedInt(bKind):
return b.Int() >= 0 && a.Uint() == uint64(b.Int())
case m.isFloat(aKind) && m.isFloat(bKind):
return a.Float() == b.Float()
default:
return a.Interface() == b.Interface()
}
}Методы
.Int(), .Uint() и .Float() расширяют любую разрядность до самой большой формы, так что int8, int16, int, int64 сворачиваются в одно сравнение. Случаи знаковое против беззнакового защищены проверкой >= 0, чтобы отрицательное число не превратилось в гигантское беззнаковое. Всё, что не число, падает в обычный ==.У рефлексии есть свои минусы, и автор перечисляет их прямо. Она медленнее прямого доступа к полям, но для реакции на события и горстки записей это неважно. Вы теряете проверку маппинга на этапе компиляции, опечатка в теге
match не уронит сборку, поле просто не сматчится, и тут спасает маленький юнит-тест на структуру. Сравниваются только плоские скалярные поля, вложенные структуры и срезы идут как один непрозрачный кусок. Неэкспортируемые поля недоступны.Весь приём это смена вопроса. Вместо «как сравнить эти две конкретные структуры» автор спросил «как сравнить любые две структуры, которые сами рассказали, как их поля соотносятся». Теги уже несли эту информацию для GORM и JSON, осталось добавить один тег и научить маленькую функцию его читать.
Получилось около 130 строк рефлексии вместо растущей кучи хрупких блоков
if. Код работает на всех моделях и становится проще в поддержке по мере роста системы. Если ловите себя на том, что пишете одну и ту же логику по полям в N-й раз, проверьте, не лежат ли метаданные для обобщения прямо в ваших тегах. Чаще, чем кажется, они там уже есть.📍 Навигация: Вакансии • Задачи • Собесы
#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
ОПРОС: Ответьте на пару вопросов и получите промокод на все наши курсы! 🎁
Команда Prоglib.аcаdemy готовит к запуску новые продукты, и нам нужна ваша помощь. Мы хотим создавать обучение, которое будет решать ваши реальные карьерные задачи и бить точно в цель.
Поделитесь своим опытом и ожиданиями, чтобы мы сделали наши курсы еще полезнее именно для вас! Заполнение анкеты займет буквально 2–3 минуты, а с нас — скидка на любой наш курс!
👉 Пройти опрос в Яндекс Формах и забрать промокод
Команда Prоglib.аcаdemy готовит к запуску новые продукты, и нам нужна ваша помощь. Мы хотим создавать обучение, которое будет решать ваши реальные карьерные задачи и бить точно в цель.
Поделитесь своим опытом и ожиданиями, чтобы мы сделали наши курсы еще полезнее именно для вас! Заполнение анкеты займет буквально 2–3 минуты, а с нас — скидка на любой наш курс!
👉 Пройти опрос в Яндекс Формах и забрать промокод
Писать ретро-эмуляторы и тестировать их это разные удовольствия. Эмулятор обычно требует окна, видеокарты, звуковой системы, и каждый прогон автотеста превращается в проблему, особенно в CI, где никакого дисплея просто нет.
Либо приходится гонять эмулятор руками и проверять результат на глаз, либо городить виртуальный X-сервер только ради одного теста.
zenzx — эмулятор ZX Spectrum на Go. Помимо обычного графического режима он умеет работать совсем без окна, просто прогонять нужное число кадров и сохранять результат в виде PNG.ZenZX поддерживает модели 48K, 128K, +2 и +3, включая контроллер дисковода у модели +3, а также загрузку с ленты и снапшоты. Собирается он в двух вариантах.
GUI-сборка по умолчанию рендерит через raylib, с поддержкой звука, и требует CGO вместе с системными библиотеками OpenGL, X11 или Wayland, и ALSA. Headless-сборка собирается с тегом
headless, не требует CGO, не открывает окно и не трогает звуковую карту. Она загружает модель, прогоняет заданное число кадров и сохраняет PNG-снимки экрана, декодированные прямо из видеопамяти.Для обычной сборки нужны Go 1.25 или новее, а для GUI-варианта ещё и C-тулчейн с raylib и oto:
./build.sh # обычный GUI-бинарник, нужны CGO и системные библиотеки
./build_headless.sh # headless-бинарник без CGO
Запуск GUI выглядит так:
./zenzx -model=48k
./zenzx -model=128k
./zenzx -model=plus3
./zenzx -tape=game.tap -tapemode=accurate
ZenZX интересен не столько как ещё один эмулятор Спектрума, а как пример того, как отделить эмуляцию от рендеринга и звука так, чтобы можно было гонять её в CI или в скрипте без единого окна.
📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤2
В коде на Go всё чаще встречается одна и та же картина, почти перед каждым обращением к указателю стоит
if x != nil. Выглядит как разумная подстраховка, но часто это сигнал, что в системе уже не понятно, какие значения действительно могут быть nil, а какие нет, и проверяется всё подряд на всякий случай.Структура
RateLimiter хранит клиент Redis:func (r *RateLimiter) Allow(ctx context.Context, req *Request) (bool, error) {
userID := GetUserID(req)
if r.redis != nil {
return r.checkLimit(ctx, userID)
}
return false, nil
}Проверка
r.redis != nil ничего не лечит. Если клиент равен nil, ошибка произошла раньше, при инициализации, и проверка здесь просто позволяет коду работать дальше в уже сломанном состоянии. В Go лучше падать сразу и громко, чем тащить невалидное состояние по системе.
Перенос проверки в конструктор
NewRateLimiter выглядит лучше, но тоже не решает задачу до конца, потому что nil всё равно успевает дойти до конструктора. Правильное место для обработки ошибки это сама точка создания клиента:redisClient, err := NewRedisClient(addr)
if err != nil {
return nil, err
}
limiter := NewRateLimiter(redisClient)
Если клиент создан успешно, дальше по системе он уже не может быть
nil, и проверки внутри RateLimiter просто не нужны. Если же системе важно переживать временную недоступность Redis, это стоит явно смоделировать отдельным типом, который сам внутри себя справляется с повторами, а наружу всегда отдаёт нечто гарантированно рабочее.Частый аргумент в пользу лишних проверок звучит так, не хочу ронять программу из-за своего изменения, лучше залогировать и продолжить. Только выбор здесь не между падением и продолжением работы, а между громким отказом и тихим.
Явная ошибка заметна сразу, привязана к месту, где она произошла, и понятно, какая операция не удалась. Проглоченная ошибка устроена наоборот, она всплывает позже, после того как уже отработал другой код, и к моменту, когда виден симптом, найти причину сложнее.
На тихие отказы потом приходится тратить силы отдельно, выстраивая метрики и алерты, чтобы заметить то, что сами же и спрятали.
Полезно думать о коде как о внешнем и внутреннем слое. Внешний слой это там, где данные входят в программу, например HTTP-обработчик. Внутренний слой это код, до которого эти данные доходят дальше по стеку вызовов:
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
req, err := DecodeRequest(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
allowed, err := h.limiter.Allow(r.Context(), req)
// ...
}Проверять запрос на
nil нужно один раз, на границе, в момент декодирования. После этого Allow уже может работать с запросом как с доверенными данными, без единой проверки на nil внутри.Проверка на
nil уместна, когда она охраняет границу системы или моделирует осознанно опциональное состояние. Если же она тихо обрабатывает состояние, которое по замыслу вообще не должно быть возможным, это уже сигнал проблемы в дизайне. Решение тогда не в том, чтобы добавить ещё проверок, а в том, чтобы постепенно установить инварианты, на которые сможет опираться остальной код.📍 Навигация: Вакансии • Задачи • Собесы
#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
🔄 Вышел gokrazy/rsync v0.3.4
В версии
➡️ Политику
➡️ Отдельно поправили совместимость с новыми версиями форка
➡️ Из менее заметного, но полезного, в релиз попал вклад нового контрибьютора, добавляющий клиенту возможность задать собственный
Если вы используете
➡️ Репозиторий
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoLive
gokrazy/rsync это реализация протокола rsync на чистом Go, и клиент, и сервер, с поддержкой передачи файлов в обе стороны. В версии
v0.3.4 решены три проблемыlandlock расширили, добавив в неё /etc, а на gokrazy ещё и /tmp, потому что без доступа к этим путям через какое-то время ломалось разрешение DNS на системах с запущенным Tailscale. openrsync от Apple, и закрыли баг, из-за которого передача файла иногда падала с ошибкой file has changed mid-transfer.net.DialContext. Это позволяет управлять тем, как именно устанавливается сетевое соединение, например проксировать его или привязывать к конкретному интерфейсу, не трогая остальной код клиента.Если вы используете
gokrazy/rsync вместе с Tailscale или синхронизируете большие файлы, которые могут меняться во время передачи, обновление до v0.3.4 стоит того, чтобы не упираться в уже известные и закрытые баги.📍 Навигация: Вакансии • Задачи • Собесы
#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6😢1
Второй параметр в
http.ListenAndServe обязателен, но на самом деле Go — это гибкий язык.Когда вы передаёте
nil в качестве хендлера, то есть несколько вариантов, которые приходят в голову: паника, 404 на все запросы или сервер попросту не запустится. 📍 Навигация: Вакансии • Задачи • Собесы
#ReadySetGo
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔3👍2
This media is not supported in your browser
VIEW IN TELEGRAM
📹 Что должен делать агент-оркестратор?
Посмотрите короткий отрывок из вебинара с Дмитрием Юдиным, внутри концентрат инсайтов о ии-агентах. А если хотите погрузиться в тему с головой, у нас доступна и полная запись этого эфира.
Готовы перейти от теории к практике и собрать свой первый продакшн-кейс?
Прямо сейчас действует мощная акция «3 курса по цене 1»:
🔥 При покупке VIP-тарифа нового потока «Разработка ИИ-агентов» вы получаете в подарок хардкорный курс «AgentOps» + ещё один любой курс Академии на ваш выбор!
Выгода в цифрах: два топовых курса по созданию и контролю агентов обойдутся вам всего в 134.000 ₽ вместо 263.000 ₽. Плюс третий курс бонусом (например, можно подтянуть математику для AI). Ваша чистая экономия —129 000 ₽!
Сомневаетесь, подойдет ли вам формат? Оставьте заявку и пройдите бесплатный демо-урок, чтобы протестировать платформу перед покупкой.
👉 Пройти демо-урок и забрать 3 курса по цене 1
Посмотрите короткий отрывок из вебинара с Дмитрием Юдиным, внутри концентрат инсайтов о ии-агентах. А если хотите погрузиться в тему с головой, у нас доступна и полная запись этого эфира.
Готовы перейти от теории к практике и собрать свой первый продакшн-кейс?
Прямо сейчас действует мощная акция «3 курса по цене 1»:
🔥 При покупке VIP-тарифа нового потока «Разработка ИИ-агентов» вы получаете в подарок хардкорный курс «AgentOps» + ещё один любой курс Академии на ваш выбор!
Выгода в цифрах: два топовых курса по созданию и контролю агентов обойдутся вам всего в 134.000 ₽ вместо 263.000 ₽. Плюс третий курс бонусом (например, можно подтянуть математику для AI). Ваша чистая экономия —
Сомневаетесь, подойдет ли вам формат? Оставьте заявку и пройдите бесплатный демо-урок, чтобы протестировать платформу перед покупкой.
👉 Пройти демо-урок и забрать 3 курса по цене 1
❤1👍1
Сейчас, если нужно использовать SIMD в Go, есть по сути один путь — писать ассемблерные вставки руками. Это медленно, легко ошибиться, а ещё такой код нельзя инлайнить и он мешает асинхронной вытесняющей многозадачности рантайма.
Для задач вроде обработки векторов, хеширования, парсинга протоколов или AI-инфраструктуры это ощутимое ограничение. Команда Go предложила решение — добавить архитектурно-специфичные SIMD-интринсики прямо в язык, без изменения синтаксиса.
Что предлагают
Идея в двухуровневом подходе. Низкий уровень — это архитектурно-специфичный пакет
simd, где операции почти один в один соответствуют машинным инструкциям. Высокий уровень — портируемый API поверх него, который появится позже. Сравнение с syscall и os тут в тему. Большинство кода будет работать с высокоуровневым API, а для редких случаев останется доступ к низкоуровневым операциям.На старте фокус на AMD64. Пробуется всё это под флагом
GOEXPERIMENT=simd, ветка называется dev.simd.Векторные типы описаны как непрозрачные структуры, компилятор распознаёт их и хранит в векторных регистрах:
type Uint32x4 struct { a0, a1, a2, a3 uint32 }
type Float64x4 struct { a0, a1, a2, a3 float64 }Операции реализованы как методы:
// Сложение элементов двух векторов.
// Соответствует инструкции VPADDD на x86.
func (Uint32x4) Add(Uint32x4) Uint32x4
Загрузка и сохранение работают через указатель на массив нужного размера:
func LoadUint32x4(*[4]uint32) Uint32x4
func (Uint32x4) Store(*[4]uint32)
Отдельно продуманы маски. На разных архитектурах маска хранится по-разному — где-то это регистр с одним битом на элемент, где-то обычный векторный регистр. Поэтому маска тоже сделана непрозрачным типом, а компилятор сам выбирает представление в зависимости от того, как маска используется дальше:
func (Uint32x4) AddMasked(Uint32x4, Mask32x4) Uint32x4 // VPADDD.Z
func (Uint32x4) Equal(Uint32x4) Mask32x4 // VCMPEQD или VPCMPD
Для проверки поддержки процессором нужных возможностей добавлены функции вроде
HasAVX512(). Компилятор считает их чистыми функциями, поскольку они не меняются после старта программы, и может использовать это для оптимизаций.Предложение сейчас в статусе active и рассматривается на еженедельных встречах по Go-предложениям. Часть операций, как Gather и Scatter, отложена на Go 1.27 из-за вопросов безопасности памяти. Портируемый высокоуровневый API и поддержка масштабируемых векторов, таких как ARM64 SVE, тоже в планах на будущее.
📍 Навигация: Вакансии • Задачи • Собесы
#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤5🔥4
🎬 Как ИИ ускоряет разработку и где ломаются архитектуры
Мы провели открытый вебинар, где разобрали реальные боли проектирования автономных систем. Ольга Лукьянова на практическом кейсе показала, как использовать ИИ-ассистентов для реальных задач. Вы просили запись встречи — она уже в открытом доступе!
Что внутри:
— Как с помощью ИИ быстрее разбираться в незнакомом коде и готовить пулл-реквесты;
— Критерии выбора между одним агентом и мультиагентной системой;
— Разбор популярных архитектурных ошибок и ограничений современных ИИ;
— Практические рекомендации по проектированию и внедрению облачных агентов.
👉 Посмотреть полную запись можно тут:
● VK
● YouTube
🚀 Хотите пойти дальше открытого вебинара? Если вы готовы перейти от простых промптов к проектированию надежных, отказоустойчивых ИИ-систем, которые не сливают бюджет компании на API, приходите на курс AgentOps. Поток уже стартовал, но двери еще приоткрыты!
👉 Успеть на курс AgentOps
Мы провели открытый вебинар, где разобрали реальные боли проектирования автономных систем. Ольга Лукьянова на практическом кейсе показала, как использовать ИИ-ассистентов для реальных задач. Вы просили запись встречи — она уже в открытом доступе!
Что внутри:
— Как с помощью ИИ быстрее разбираться в незнакомом коде и готовить пулл-реквесты;
— Критерии выбора между одним агентом и мультиагентной системой;
— Разбор популярных архитектурных ошибок и ограничений современных ИИ;
— Практические рекомендации по проектированию и внедрению облачных агентов.
👉 Посмотреть полную запись можно тут:
● VK
● YouTube
🚀 Хотите пойти дальше открытого вебинара? Если вы готовы перейти от простых промптов к проектированию надежных, отказоустойчивых ИИ-систем, которые не сливают бюджет компании на API, приходите на курс AgentOps. Поток уже стартовал, но двери еще приоткрыты!
👉 Успеть на курс AgentOps
👍1
📋 Чек-лист из 8 признаков, что вас скоро уволят
Услышали на созвоне слово «оптимизация» больше трех раз? Поздравляем, Вы уже в процессе.
Статья про то, как компания вежливо, но разрушительно, готовит вас к уходу: задачи исчезают, не зовут на встречи, условия труда волшебным образом меняются, а повышение замораживают на бесконечность. Все это не совпадение, все это признаки.
➡️ Пройтись по чек-листу
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
Услышали на созвоне слово «оптимизация» больше трех раз? Поздравляем, Вы уже в процессе.
Статья про то, как компания вежливо, но разрушительно, готовит вас к уходу: задачи исчезают, не зовут на встречи, условия труда волшебным образом меняются, а повышение замораживают на бесконечность. Все это не совпадение, все это признаки.
📍 Навигация: Вакансии • Задачи • Собесы
Please open Telegram to view this post
VIEW IN TELEGRAM
🧑💻 В Go предлагают лимит на количество HTTP заголовков
В репозитории Go обсуждают новое поле
Сейчас сервер
Реализация HTTP/2 в Go к DoS атаке устойчива. Сервер закладывает 32 байта накладных расходов на каждое значение заголовка при подсчёте
Предлагаемое решение выглядит так:
По умолчанию сервер примет не больше 500 значений заголовков на запрос. Считают именно значения, а не имена. Иначе атакующий мог бы прислать один заголовок с огромным числом пустых значений и обойти лимит.
Ждать релиза Go 1.28 необязательно. Автор предложил переходный вариант через переменную окружения, он оформлен отдельным issue 80020. Запуск выглядит так.
Значение из
Если у вас сервис на Go с большим
➡️ Наша новостная рассылка не имеет лимитов
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoLive
В репозитории Go обсуждают новое поле
Server.MaxHeaderValueCount в пакете net/http. Статус уже Likely Accept, идёт финальный период комментариев. Если всё пройдёт гладко, поле появится в Go 1.28.Сейчас сервер
net/http ограничивает заголовки только по общему размеру через MaxHeaderBytes. Отдельного лимита на их количество нет. Этим пользуются атаки типа DoS. Злоумышленник шлёт тысячи мелких заголовков, например с именем в один байт без значения. На проводе такой заголовок почти ничего не весит, но во внутренних структурах сервера каждый занимает заметно больше памяти. Реализация HTTP/2 в Go к DoS атаке устойчива. Сервер закладывает 32 байта накладных расходов на каждое значение заголовка при подсчёте
MaxHeaderBytes. Но пользователи всё равно в неудобном положении. Кому нужны большие заголовки, скажем SSO куки на несколько килобайт, приходится поднимать MaxHeaderBytes. Вместе с лимитом растёт и число мелких заголовков, которые сервер готов принять. Слишком большой лимит открывает дорогу DoS, слишком маленький ломает легитимные запросы. Один параметр на два разных риска.Предлагаемое решение выглядит так:
package http
// DefaultMaxHeaderValueCount is the maximum permitted number of
// header values in an HTTP request.
// This can be overriden by setting [Server.MaxHeaderValueCount]
const DefaultMaxHeaderValueCount = 500
type Server struct {
// Other fields omitted...
// MaxHeaderValueCount controls the maximum number of header
// values that the server is willing to parse from a request.
// If zero, DefaultMaxHeaderValueCount is used.
MaxHeaderValueCount int
}
По умолчанию сервер примет не больше 500 значений заголовков на запрос. Считают именно значения, а не имена. Иначе атакующий мог бы прислать один заголовок с огромным числом пустых значений и обойти лимит.
Ждать релиза Go 1.28 необязательно. Автор предложил переходный вариант через переменную окружения, он оформлен отдельным issue 80020. Запуск выглядит так.
GODEBUG=httpmaxheadervalues=500 ./your-server
Значение из
GODEBUG задаёт дефолт, а явно выставленное поле в Server будет иметь приоритет.Если у вас сервис на Go с большим
MaxHeaderBytes, стоит следить за этим proposal. Новый параметр позволит держать оба лимита раздельно и защищаться от лавины мусорных заголовков без риска сломать реальный трафик. 📍 Навигация: Вакансии • Задачи • Собесы
#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤1
Первый июльский дайджест
— GitHub предлагает выслать код на CD-диске
GitHub иронично отреагировал на решение Sony прекратить выпуск дисков для PlayStation. Теперь разработчики могут заполнить специальную форму и получить свой публичный репозиторий записанным на физический CD-ROM.
— 1 часть цикла постов про сравнение двух структур
— Учимся уходить красиво
— gokrazy/rsync v0.3.4
📍 Навигация: Вакансии • Задачи • Собесы
#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
Anonymous Poll
40%
Почитаю, ведь у меня есть дисковод
52%
У меня даже дисковода нет
8%
Что такое CD-диск?