Вы используете устаревший браузер. Этот и другие сайты могут отображаться в нём некорректно. Вам необходимо обновить браузер или попробовать использовать другой.
Всем привет, в моей прошлой теме тыц, мы успели пообсуждать кто, что и как пишет.
В итоге я решил что пора бы уже делать что-то, что можно было бы и людям показать.
Посему предлагаю организовать команду, чтобы совместно сделать качественный продукт.
Конечно, не все захотят тратить время, а кому-то денег подавай, но тут каждому свое. Если хотите внести вклад — милости просим.
Сразу скажу: Java, Go
Для начала я выкатил в репозиторий только логин-сервер. Но даже по нему уже можно посмотреть, насколько я упоролся.
Если ты такой же упоротый — ты тот, кто нужен команде mmo-dev team!
З.Ы. Зачатки гейм-сервера залью чуть позже.
github:
Вы не можете просматривать ссылку пожалуйста воспользуйтесь следующими ссылками Вход или Регистрация
Та самая которую тут эмулируют джавой или в данном случае Go. Думаешь надмозги из NCSoft с их миллионами и бюджетами не распланировали какая архитектура и какой язык нужен для сервера? Они точно не монетку там кидали. В контексте же тематики данного форума а это эмуляция то джава вполне себе вытягивает все это непотребство, вот человек проверяет на Go а я в свое время видел вообще свистопляску на Питоне, там черт ногу сломает. Нет преград для патриотов своего дела. Вот и выясним а точно ли только джава или еще что то.
Та самая которую тут эмулируют джавой или в данном случае Go. Думаешь надмозги из NCSoft с их миллионами и бюджетами не распланировали какая архитектура и какой язык нужен для сервера? Они точно не монетку там кидали. В контексте же тематики данного форума а это эмуляция то джава вполне себе вытягивает все это непотребство, вот человек проверяет на Go а я в свое время видел вообще свистопляску на Питоне, там черт ногу сломает. Нет преград для патриотов своего дела. Вот и выясним а точно ли только джава или еще что то.
Джава вполне себе хорошо себя чувствует, взять опыт коропораций где нагрузка измеряется сотнями или тысячами rps - там везде java/kotlin
нцсофт вбухали кучу денег в свое монолитное решение и как я понял особо архитектуру они не поменяли, да и зачем, если она работает годами и приносит деньги. Поэтому их сервера сжирают ресурсы лопатами и масштабируются только вертикально, что в современном мире уже не нужно, возможно в последних хрониках они докрутили хотя бы базовую репликацию, но что-то мне кажется нет
Я то думал C, C++, Rust для высоконагруженных используют, а тут оказывается java/kotlin . А там где нагрузка измеряется сотнями ли тысячами начинается лишь байтоёбство и забыванием многих фич языка и по памяти все равно проигрывает, разогнанный JIT и выжранная память, поможет приблизиться к данным языкам по скорости работы в некоторых операциях, но какой ценой? А для более высокоуровневого есть C#, но уж не java
нцсофт вбухали кучу денег в свое монолитное решение и как я понял особо архитектуру они не поменяли, да и зачем, если она работает годами и приносит деньги.
NCSoft по сути опередили своё время с архитектурой, и далеко не монолит они делали уже тогда у нас были разделены логин, гейм, нпц сервер, репортинг сервер, сервер петиций, CacheD и не только, так что под описание монолита никак не подвязать, это SOA(Service-Oriented Architecture).
Поэтому их сервера сжирают ресурсы лопатами и масштабируются только вертикально, что в современном мире уже не нужно, возможно в последних хрониках они докрутили хотя бы базовую репликацию, но что-то мне кажется нет
Не поэтому, а другие причины, а переписывать всю архитектуру, весь код и современные подходы не имеет никакой выгоды, на 2026 такие ресурсы вообще не колоссальные, java со своим пожиранием памяти куда больше потратит ресурсов.
NCSoft по сути опередили своё время с архитектурой, и далеко не монолит они делали уже тогда у нас были разделены логин, гейм, нпц сервер, репортинг сервер, сервер петиций, CacheD и не только, так что под описание монолита никак не подвязать, это SOA(Service-Oriented Architecture).
Да это похоже на SOA, но какая разница как это называется, если оно в простое жрет ресурсы, как монолит который разрабатывался годами? Делили бы дальше свои сервисы для масштабирования
kick, вряд ли они гнались за громкими названиями и/или абревиатурами, делали то что позволяло железо/софт тех лет.
отличную тему я создал, обсуждаем все, но только не сабж
Да это похоже на SOA, но какая разница как это называется, если оно в простое жрет ресурсы, как монолит который разрабатывался годами? Делили бы дальше свои сервисы для масштабирования
В java проектах л2 нет архитектуры, это лишь пример, как не надо делать. Для обучения, это колоссальный объем знаний, который нужен на сегодняшний день с пониманием всех инструментов, базовых принципов как всё работает, концепции хранения данных, масштабирование, кэирование, системы сообщений, часть знаний от бизнес-аналитики, так же как популярные гиганты решают определенные проблемы, с чем сталкиваются, доклады их. Многие показывают готовые архитектуры, схемы, уже брать за основу, от того же netflix не мало статей.
В чём?) Как то резко изменилась jvm?) Нет, сколько уже сборщиков мусора сменилась в jvm? 7 и до сих пор не могут заставить работать их нормально. У JVM нет контроля, там где он нужен, потому что она забирает ответственность на себя и решает за программиста где и что она будет оптимизировать. То что java сейчас это отстающая среда, пока комитет java примет новую фичу.
Java:
import java.util.UUID;
class Node {
String id;
Integer value;
Node left;
Node right;
Node(int depth) {
this.id = UUID.randomUUID().toString();
this.value = depth;
if (depth > 0) {
this.left = new Node(depth - 1);
this.right = new Node(depth - 1);
}
}
}
public class Main {
public static void main(String[] args) {
Node root = new Node(25);
}
}
И вот будут чудеса и особенно с автоупаковкой int -> Integer и тоже самое, что даже не почувствуется.
C++:
#include <iostream>
#include <memory>
#include <string>
struct Node {
char id[36];
int value;
std::unique_ptr<Node> left;
std::unique_ptr<Node> right;
Node(int depth) : value(depth) {
int8_t dummy[] = "12345678-1234-1234-1234-123456789012";
std::copy(std::begin(dummy), std::end(dummy), id);
if (depth > 0) {
left = std::make_unique<Node>(depth - 1);
right = std::make_unique<Node>(depth - 1);
}
}
};
int main() {
auto root = std::make_unique<Node>(25);
return 0;
}
Про аллокации лямбд и не только думаю мне не надо, я могу даже дать задачу вот такую, что максимум на плюсах выжмет 30 мегабайт.
Дан массив из 1000000 объектов. Каждый объект представляет собой точку и содержит:
Координаты (x, y, z)
Флаг состояния (isActive)
Цель: пробежать по всему массиву и для всех активных точек (isActive == true) посчитать общую сумму их координат.
Типы данных: внутри объекта координаты должны быть объявлены как Double, а флаг, как Boolean (объектные типы-обертки).
Контейнер: сам массив должен быть реализован в виде связного списка (LinkedList), а проверка флага на активность должна проходить через Optional.
Логика вычислений: расчет суммы для каждой точки должен выполняться через вызов метода, в который передается динамическая лямбда. Лямбда обязана захватывать переменную текущего индекса элемента из внешнего цикла в качестве модификатора, причем сам индекс перед передачей должен быть приведен к объектному типу Integer.
Агрегация результата: итоговый результат общей суммы координат всех активных точек должен накапливаться и храниться в переменной объектного типа Double.
Java:
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
class Point {
public Double x;
public Double y;
public Double z;
public Boolean isActive;
public Point(double x, double y, double z, boolean isActive) {
this.x = x;
this.y = y;
this.z = z;
this.isActive = isActive;
}
}
public class Main {
private static double calculate(Point p, Function<Point, Double> formula) {
return formula.apply(p);
}
public static void main(String[] args) {
List<Point> points = new LinkedList<>();
for (int i = 0; i < 1_000_000; i++) {
points.add(new Point(i * 1.1, i * 1.2, i * 1.3, i % 2 == 0));
}
long startTime = System.nanoTime();
Double totalSum = 0.0;
int index = 0;
for (Point p : points) {
Integer stepId = index;
final double modifier = (stepId % 2 == 0) ? 1.0 : 1.005;
if (Optional.ofNullable(p.isActive).orElse(false)) {
totalSum += calculate(p, pt -> (pt.x + pt.y + pt.z) * modifier);
}
index++;
}
long endTime = System.nanoTime();
System.out.println("Результат: " + totalSum);
System.out.println("Время выполнения: " + (endTime - startTime) / 1_000_000.0 + " мс");
}
}
153 мегабайт на хранение миллиона точек это очень много, для плюсов это копейки и это специально так поставлены задачи и код, но это имеет место быть и как раз то что происходит потом на hot-path при захвате лямбы, начинается веселье.
А ещё бывают такие места где хоть убей тебе нужно удалить объект, а этого не сделать, потому что JVM решает за тебя это, а в C# всё решается
Код:
using System.Diagnostics;
using System.Runtime.InteropServices;
public struct Point
{
public double X;
public double Y;
public double Z;
public bool IsActive;
}
public static class Program
{
public static void Main()
{
List<Point> points = new List<Point>(1_000_000);
for (int i = 0; i < 1_000_000; i++)
{
points.Add(new Point { X = i * 1.1, Y = i * 1.2, Z = i * 1.3, IsActive = (i % 2 == 0) });
}
Stopwatch sw = Stopwatch.StartNew();
double totalSum = 0.0;
Span<Point> pointSpan = CollectionsMarshal.AsSpan(points);
for (int i = 0; i < pointSpan.Length; i++)
{
ref readonly Point p = ref pointSpan[i];
if (p.IsActive)
{
double modifier = (i % 2 == 0) ? 1.0 : 1.005;
totalSum += (p.X + p.Y + p.Z) * modifier;
}
}
sw.Stop();
Console.WriteLine($"Результат: {totalSum}");
Console.WriteLine($"Время выполнения в C#: {sw.Elapsed.TotalMilliseconds:F2} мс");
}
}
Ну и плюсы:
C++:
#include <iostream>
#include <list>
#include <optional>
#include <chrono>
struct Point {
double x;
double y;
double z;
std::optional<bool> isActive;
};
template<typename F>
double calculate(const Point& p, F formula) {
return formula(p);
}
int main() {
std::list<Point> points;
for (int i = 0; i < 1000000; ++i) {
points.push_back({ i * 1.1, i * 1.2, i * 1.3, (i % 2 == 0) });
}
auto start = std::chrono::high_resolution_clock::now();
std::optional<double> total_sum = 0.0;
int index = 0;
for (const auto& p : points) {
int step_id = index;
double modifier = (step_id % 2 == 0) ? 1.0 : 1.005;
if (p.isActive.value_or(false)) {
total_sum.value() += calculate(p, [&](const Point& pt) {
return (pt.x + pt.y + pt.z) * modifier;
});
}
index++;
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> duration = end - start;
std::cout << "Результат: " << total_sum.value() << "\n";
std::cout << "Время выполнения в C++: " << duration.count() << " мс\n";
return 0;
}
Ну по памяти прекрасно видим на самом плохом примере, на каждом из языков можно лучше в разы сделать и без таких условий, а как пишут код и какие люди на собесы и все те же болячки присутствуют, конечно можно писать правильно и как надо, но некоторые даже не задумываются о особенностях или пишут, а потом ловят на проде приколы. Есть много особенностей в языке и чтобы начинать экономить память, так и написано
Потому что если начать тоже делать в си/плюсах/расте/шарпе будет все равно выигрывать по памяти. Я может быть и могу согласиться с выражением, что java больше не такая медленная как была раньше, да это так и есть и в некоторых моментах она даже может обойти плюсы, это так, но опять таки при каких условиях это достигается и из-за чего. Но по памяти все ещё актуально
kick, ничего не понял, в C# читерить можно а в Go нет.
Есть цель показать результаты как рантайм в хреновых условиях вывезет эту катку, то нужно сделать равные условия для всех, а не выделять C#.
kick, ничего не понял, в C# ты читеришь а в Go показываешь как делать не нужно.
Есть цель показать результаты как рантайм в хреновых условиях вывезет эту катку, то нужно сделать равные условия для всех, а не выделять C#.
Да вообще не проблема, я могу не использовать структуры, дописать, чтобы проверка была аналогична условию, от неё ничего не изменится, все ограничения задачи и поставлены выполняются. Перебираю коллекция в цикле foreach, получаю полноценные объекты Point, работаю со свойствами-обёртками (double?, bool?), проверяю их через аналог Optional (GetValueOrDefault) и вызываю метод через лямбду с упаковкой индекса в int?
Вся форма кода осталась оригинальной и оригинальной джавовской задачей.
C#:
using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
public class Point
{
public double? x;
public double? y;
public double? z;
public bool? isActive;
public void Reset(double x, double y, double z, bool isActive)
{
this.x = x;
this.y = y;
this.z = z;
this.isActive = isActive;
}
}
public class LinkedListNode
{
public Point Item;
public LinkedListNode Next;
}
public struct CustomEnumerator : IEnumerator<Point>
{
private LinkedListNode _current;
private bool _started;
public CustomEnumerator(LinkedListNode head)
{
_current = head;
_started = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
if (!_started)
{
_started = true;
return _current != null;
}
if (_current != null)
{
_current = _current.Next;
}
return _current != null;
}
public Point Current => _current != null ? _current.Item : null;
object IEnumerator.Current => Current;
public void Reset() => throw new NotSupportedException();
public void Dispose() { }
}
public class VirtualLinkedList : IReadOnlyCollection<Point>
{
private LinkedListNode _head;
private LinkedListNode _tail;
public int Count { get; private set; } = 0;
private readonly LinkedListNode[] _nodePool = new LinkedListNode[1_000_000];
private readonly Point[] _pointPool = new Point[1_000_000];
public VirtualLinkedList()
{
for (int i = 0; i < 1_000_000; i++)
{
_nodePool[i] = new LinkedListNode();
_pointPool[i] = new Point();
}
}
public void AddLast(double x, double y, double z, bool isActive)
{
Point p = _pointPool[Count];
p.Reset(x, y, z, isActive);
LinkedListNode newNode = _nodePool[Count];
newNode.Item = p;
newNode.Next = null;
if (_head == null)
{
_head = newNode;
}
else
{
_tail.Next = newNode;
}
_tail = newNode;
Count++;
}
public CustomEnumerator GetEnumerator() => new CustomEnumerator(_head);
IEnumerator<Point> IEnumerable<Point>.GetEnumerator() => new CustomEnumerator(_head);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public readonly struct CalculationContext
{
public readonly double Modifier;
public CalculationContext(double modifier) => Modifier = modifier;
}
public static class Program
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double Calculate(Point p, in CalculationContext ctx, Func<Point, CalculationContext, double> formula)
{
return formula(p, ctx);
}
public static void Main()
{
VirtualLinkedList points = new VirtualLinkedList();
for (int i = 0; i < 1_000_000; i++)
{
points.AddLast(i * 1.1, i * 1.2, i * 1.3, i % 2 == 0);
}
Stopwatch sw = Stopwatch.StartNew();
double? totalSum = 0.0;
double localSum = 0.0;
int index = 0;
Func<Point, CalculationContext, double> formula = (pt, ctx) => (pt.x.Value + pt.y.Value + pt.z.Value) * ctx.Modifier;
foreach (Point p in points)
{
int? stepId = index;
double modifier = (stepId % 2 == 0) ? 1.0 : 1.005;
if (p.isActive.GetValueOrDefault(false))
{
var context = new CalculationContext(modifier);
localSum += Calculate(p, in context, formula);
}
index++;
}
totalSum = localSum;
sw.Stop();
Console.WriteLine($"Результат: {totalSum}");
Console.WriteLine($"Время выполнения в C#: {sw.Elapsed.TotalMilliseconds:F2} мс");
}
}
Или мне убрать Enumerator?)
Никто не запрещал использовать в Go колоночное хранение и паттерн легковес, но ты же код не показал.
Я могу использовать структуры, упадёт до 62 мегабайт спокойно
Да вообще не проблема, я могу не использовать структуры, дописать, чтобы проверка была аналогична условию, от неё ничего не изменится, все ограничения задачи и поставлены выполняются. Перебираю коллекция в цикле foreach, получаю полноценные объекты Point, работаю со свойствами-обёртками (double?, bool?), проверяю их через аналог Optional (GetValueOrDefault) и вызываю метод через лямбду с упаковкой индекса в int? Посмотреть вложение 96608
Вся форма кода осталась оригинальной и оригинальной джавовской задачей.
C#:
using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
public class Point
{
private readonly VirtualStorage _storage;
internal int _index;
public double? x => _storage.XArray[_index];
public double? y => _storage.YArray[_index];
public double? z => _storage.ZArray[_index];
public bool? isActive => _storage.ActiveArray[_index];
public Point(VirtualStorage storage, int index)
{
_storage = storage;
_index = index;
}
}
public class VirtualStorage
{
public readonly double[] XArray = new double[1_000_000];
public readonly double[] YArray = new double[1_000_000];
public readonly double[] ZArray = new double[1_000_000];
public readonly bool[] ActiveArray = new bool[1_000_000];
}
public class VirtualLinkedList : IReadOnlyCollection<Point>
{
private readonly VirtualStorage _storage;
public int Count => 1_000_000;
public VirtualLinkedList(VirtualStorage storage) => _storage = storage;
public Enumerator GetEnumerator() => new Enumerator(_storage);
IEnumerator<Point> IEnumerable<Point>.GetEnumerator() => new Enumerator(_storage);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public struct Enumerator : IEnumerator<Point>
{
private readonly VirtualStorage _storage;
private int _currentIdx;
public Point Current { get; }
public Enumerator(VirtualStorage storage)
{
_storage = storage;
_currentIdx = -1;
Current = new Point(storage, 0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
_currentIdx++;
if (_currentIdx < 1_000_000)
{
Current._index = _currentIdx;
return true;
}
return false;
}
object IEnumerator.Current => Current;
public void Reset() => _currentIdx = -1;
public void Dispose() { }
}
}
public readonly struct CalculationContext
{
public readonly double Modifier;
public CalculationContext(double modifier) => Modifier = modifier;
}
public static class Program
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double Calculate(Point p, in CalculationContext ctx, Func<Point, CalculationContext, double> formula)
{
return formula(p, ctx);
}
public static void Main()
{
VirtualStorage storage = new VirtualStorage();
for (int i = 0; i < 1_000_000; i++)
{
storage.XArray[i] = i * 1.1;
storage.YArray[i] = i * 1.2;
storage.ZArray[i] = i * 1.3;
storage.ActiveArray[i] = (i % 2 == 0);
}
VirtualLinkedList points = new VirtualLinkedList(storage);
Stopwatch sw = Stopwatch.StartNew();
double? totalSum = 0.0;
double localSum = 0.0;
int index = 0;
Func<Point, CalculationContext, double> formula = (pt, ctx) => (pt.x.Value + pt.y.Value + pt.z.Value) * ctx.Modifier;
foreach (Point p in points)
{
int? stepId = index;
double modifier = (stepId % 2 == 0) ? 1.0 : 1.005;
if (p.isActive.GetValueOrDefault(false))
{
var context = new CalculationContext(modifier);
localSum += Calculate(p, in context, formula);
}
index++;
}
totalSum = localSum;
sw.Stop();
Console.WriteLine($"Результат: {totalSum}");
Console.WriteLine($"Время выполнения в C#: {sw.Elapsed.TotalMilliseconds:F2} мс");
}
}
Или мне убрать Enumerator?)
Никто не запрещал использовать в Go колоночное хранение и паттерн легковес, но ты же код не показал
Есть облачный API который принимает UUID и возвращает определенное число от 0 до 10.
Также есть синхронный клиент, который предоставляет публичный метод для работы с этим API.
Каждый вызов метода блокирует вызывающий его поток на время от 1 до 5 секунд. Асинхронной версии метода не существует(увы, легаси). Апи не применяет рейт лимит и может принять любое количество запросов одновременно.
Для каждого UUID также заранее создается уникальный, неизменяемый контекст безопасности, содержащий идентификатор запроса и токен доступа. Публичный метод клиента принимает только UUID, но контекст должен быть неявно доступен клиенту и всем его внутренним методам на любой глубине стека. Контекст должен быть привязан только на время конкретного вызова, автоматически исчезать после его завершения или исключения, а при вложенном вызове автоматически восстанавливать предыдущие значение. Если клиент получит некорректный контекст, то апи отклонит запрос. Сигнатуру метода клиента менять нельзя из-за политики безопасности. Клиент принимает только UUID.
Можно использовать любые доступные в стандартной среде исполнения потоки, пулы потоков, задачи, экзекуторы и планировщики и штатные механизмы динамического контекста. Массив с уникальными UUID и токенами создается до начала измерения. Измеряется время от отправки первого запроса до получения суммы результатов всех 10 000 000 запросов. Писать альтерантивный клиент или модифицировать работу существующего клиента запрещено из-за строгих стандартов безопасности.
Java:
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.LongAdder;
public class Main {
static final int N = 10_000_000;
static final ScopedValue<RequestContext> CONTEXT = ScopedValue.newInstance();
record RequestContext(UUID requestId, UUID token) {}
static class Client {
int query(UUID id) {
var context = CONTEXT.get();
if (!context.requestId().equals(id) || context.token() == null) {
throw new SecurityException();
}
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5001));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return Math.floorMod(id.hashCode(), 11);
}
}
public static void main(String[] args) {
var ids = new UUID[N];
var contexts = new RequestContext[N];
for (int i = 0; i < N; i++) {
ids[i] = UUID.randomUUID();
contexts[i] = new RequestContext(ids[i], UUID.randomUUID());
}
var client = new Client();
var sum = new LongAdder();
long start = System.nanoTime();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < N; i++) {
var id = ids[i];
var context = contexts[i];
executor.submit(() -> ScopedValue.where(CONTEXT, context)
.run(() -> sum.add(client.query(id))));
}
}
System.out.println(sum.sum());
System.out.printf("%.3f s%n", (System.nanoTime() - start) / 1e9);
}
}
Код:
java --version
openjdk 26.0.1 2026-04-21
OpenJDK Runtime Environment Temurin-26.0.1+8 (build 26.0.1+8)
OpenJDK 64-Bit Server VM Temurin-26.0.1+8 (build 26.0.1+8, mixed mode, sharing)
java .\Main.java
50012355
32,628 s
Ну да) Там шарпы. Или гоу, или плюсы, или раст, или питон, или пхп, или еще чет.
Лан, не легаси. Упростим: "Вайбкодил чувак, который прошел собес через ИИ и два месяца успешно прикидывался синьором. Тендер на написание нового клиента уже разместили, но откликов пока нет. А отчет нужен через 10 минут.".
И ваще, пиши код, а не отговорки. Уже давно бы написал нормально на богоподобном шарпе. Он вон яву ебет в хвост и в гриву по твоим тестам) Я думаю там делов на 5 минут + секунд 10 на выполнение кода
Ну с легаси согласен могут быть проблемы, но и пример использует Java 21+ который далеко не легаси , а там уже есть Swoole, FrankenPHP и RoadRunner, но никто не запрещает сделать на сях расширение и дергать сишное расширение.
На данном сайте используются файлы cookie, чтобы персонализировать содержимое и сохранить Ваш вход в систему, если Вы зарегистрируетесь.
Продолжая использовать этот сайт, Вы соглашаетесь на использование наших файлов cookie.