Search code examples
c#stringperformance.net-4.5.net-8.0

Parsing string in .NET Core is slower than in .NET framework


I have this legacy code:

public class ZQLToken
{
    public ZQLTokenType TokenType = ZQLTokenType.KeyWord;
    public string Text;
    public char NextSeparator = '\0';
    public char PrevSeparator = '\0';
    public ZQLToken PrevToken;
    public ZQLToken NextToken;
    public int Line = 0;
    public int Index = 0;
}

public enum ZQLTokenType
{
    KeyWord,
    Variable,
    Operator
}

public class myclass
{
    int _lineCounter = 0;
    int _indexCounter = 0;
    char[] _separators = new char[] { ' ', '.', ',', '\n', '\t', '\r', '(', ')' };
    static string[] _operators = new string[] { "!=", "=", "<>", "<", ">", "+" };
    static string[] _lOperators = new string[] { "!=", "=", "<>", "<", ">", "+" };

    bool _caseSensitive = false;

    List<string> _keyWords = new List<string>(new string[] { "NOT_EMPTY", "DISTINCT", "TABLE", "DROP", "APPEND", "SELECT", "WHERE", "FROM", "AND", "OR", "AS", "INTO", "XML_ATT", "XML_VAL", "GROUP", "BY", "ORDER", "NUMBER" });

    public List<ZQLToken> GetTokens(string text)
    {
        List<ZQLToken> ret = new List<ZQLToken>();
        string rText = text;

        _indexCounter = 0;
        _lineCounter = 1;

        string[] tks = text.Split(_separators, StringSplitOptions.RemoveEmptyEntries);

        foreach (string ftk in tks)
        {
            char pSep = '\0';
            char nSep = '\0';

            int idx = rText.IndexOf(ftk);

            if (idx > 0)
                pSep = rText[idx - 1];

            if (idx + ftk.Length < rText.Length)
                nSep = rText[idx + ftk.Length];

            rText = rText.Substring(idx + ftk.Length);
            _indexCounter = text.IndexOf(rText);

            string[] moreTk = ftk.Split(_operators, StringSplitOptions.RemoveEmptyEntries);

            string startOp = "";

            foreach (string oper in _operators)
            {
                if (ftk.StartsWith(oper, StringComparison.Ordinal))
                    startOp = oper;
            }

            if (startOp != "")
            {
                string tok = ftk.Substring(startOp.Length);
                AddToken(ret, startOp, pSep, '\0');
                AddToken(ret, tok, '\0', nSep);
            }
            else if (moreTk.Length >= 2)
            {
                string rFtk = ftk;
                string tk1 = moreTk[0];
                AddToken(ret, tk1, pSep, '\0');

                for (int i = 1; i < moreTk.Length; i++)
                {
                    string tk2 = moreTk[i];
                    int idx2 = rFtk.IndexOf(tk2, tk1.Length);

                    string op = rFtk.Substring(tk1.Length, Math.Abs(idx2 - tk1.Length));

                    AddToken(ret, op, '\0', '\0');

                    if (i + 1 == moreTk.Length)
                        AddToken(ret, tk2, '\0', nSep);
                    else
                        AddToken(ret, tk2, '\0', '\0');

                    rFtk = rFtk.Substring(idx2);
                    tk1 = tk2;
                }
            }
            else
                AddToken(ret, moreTk[0], pSep, nSep);
        }

        return ret;
    }

    private void AddToken(List<ZQLToken> ret, string tk, char pSep, char nSep)
    {
        if (tk == "")
            return;

        ZQLToken token = new ZQLToken();
        token.Line = _lineCounter;
        token.Index = _indexCounter;

        if (ret.Count > 0)
        {
            ZQLToken ptok = ret[ret.Count - 1];
            ptok.NextToken = token;
            token.PrevToken = ptok;
        }

        string utk = tk;

        token.PrevSeparator = pSep;
        token.NextSeparator = nSep;

        if (!_caseSensitive)
            utk = tk.ToUpper();

        if (_keyWords.Contains(utk))
            token.TokenType = ZQLTokenType.KeyWord;
        else if (_lOperators.Contains(utk))
            token.TokenType = ZQLTokenType.Operator;
        else
            token.TokenType = ZQLTokenType.Variable;

        token.Text = tk;

        if (token.NextSeparator == '\n')
            _lineCounter++;

        ret.Add(token);
    }
}

And using this string as GetTokens input parameter:

SELECT DISTINCT a.id_rete idPS, 'ReteCadutaMassi' nomeTipoEnte, translate(a.rete,' ','') nomeEnte, a.punto punto
INTO tmpRetiCM
FROM tabella_reti_caduta_massi a

SELECT 'PuntoDiLinea' nomeTipoEnte, a.nome nomeEnte, a.idPS idPS
INTO tmpPuntiLinea
FROM punto a
WHERE a.tipo = LINEA OR a.tipo = LINEA SENZA LUCE
APPEND SELECT 'PuntoDiLinea' nomeTipoEnte, a.nome nomeEnte, a.idPS idPS
INTO tmpPuntiLinea
FROM punto a, blocco b
WHERE a.idPS = b.id_scudetto AND b.tipo = 'CONSENSO' AND b.classe = 'PARTENZA'
APPEND SELECT 'PuntoDiLinea' nomeTipoEnte, a.nome nomeEnte, a.idPS idPS
INTO tmpPuntiLinea
FROM punto a, blocco b
WHERE a.idPS = b.id_scudetto AND b.tipo = 'CONSENSO' AND b.classe = 'ARRIVO'

SELECT 'PuntoDiLinea' nomeTipoEnte, a.nome nomeEnte, a.idPS idPS
INTO tmpPuntoConfinePreCount
FROM punto a, blocco b
WHERE a.idPS = b.id_scudetto AND b.tipo = 'CONSENSO' AND b.classe = 'PARTENZA'
APPEND SELECT 'PuntoDiLinea' nomeTipoEnte, a.nome nomeEnte, a.idPS idPS
INTO tmpPuntoConfinePreCount
FROM punto a, blocco b
WHERE a.idPS = b.id_scudetto AND b.tipo = 'CONSENSO' AND b.classe = 'ARRIVO'
APPEND SELECT 'Punto' nomeTipoEnte, b.nome nomeEnte, a.idPS idPS
INTO tmpPuntoConfinePreCount
FROM punto_confine a, punto b
WHERE a.idPS = b.idPS

SELECT DISTINCT a.idPS idPS, COUNT(a.nomeEnte) cnt
INTO tmpPuntoConfineCount
FROM tmpPuntoConfinePreCount a
WHERE a.idPS != '' 

GROUP BY idPS
SELECT a.idPS idPS, a.cnt cnt, '-' cifra
INTO tmpPuntoConfineNoCifra
FROM tmpPuntoConfineCount a

SELECT a.idPS idPS, a.cnt cnt, b.valore cifra
INTO tmpPuntoConfineCifra
FROM tmpPuntoConfineCount a, enti_attrs b 
WHERE a.idPS = b.ente_id AND b.chiave = 'CIFRA'

SELECT DISTINCT a.idPS idPS, a.cnt cnt, a.cifra cifra
INTO tmpPuntoConfinePreCountCifra
FROM tmpPuntoConfineNoCifra a

APPEND SELECT DISTINCT a.idPS idPS, a.cnt cnt, a.cifra cifra
INTO tmpPuntoConfinePreCountCifra
FROM tmpPuntoConfineCifra a

SELECT DISTINCT a.idPS idPS, COUNT(a.cifra) cnt
INTO tmpPuntoConfineCountCifra
FROM tmpPuntoConfinePreCountCifra a
WHERE a.idPS != '' 
    
GROUP BY idPS

SELECT a.nome nome, s.idPS idSA, e.valore testo
INTO tmpCartelloSA
FROM enti a, segnale_alto s, enti_attrs e 
WHERE a.tipo = 'CARTELLO' AND a.nome = s.nome AND e.ente_id = a.id_ente AND e.chiave = 'TESTO'


SELECT 'PuntoDiLinea' nomeTipoEnte, b.nome nomeEnte, a.idPS idPS, 'SI' escludibile, a.cifra cifra
INTO tmpPunti
FROM punto_linea a, punto b
WHERE a.idPS = b.idPS
APPEND SELECT 'PuntoIntermedio' nomeTipoEnte, b.nome nomeEnte, a.idPS idPS, 'NO' escludibile, '-' cifra
INTO tmpPunti
FROM punto_intermedio a, punto b
WHERE a.idPS = b.idPS
APPEND SELECT 'Punto' nomeTipoEnte, b.nome nomeEnte, a.idPS idPS, 'NO' escludibile, '-' cifra
INTO tmpPunti
FROM punto_confine a, punto b, tmpPuntoConfineCount c
WHERE a.idPS = b.idPS AND a.idPS = c.idPS AND c.cnt != 2
APPEND SELECT 'PuntoDiLinea' nomeTipoEnte, b.nome nomeEnte, a.idPS idPS, 'SI' escludibile, '-' cifra
INTO tmpPunti
FROM punto_confine a, punto b, tmpPuntoConfineCount c, tmpPuntoConfineCountCifra d
WHERE a.idPS = b.idPS AND a.idPS = c.idPS AND c.cnt = 2 AND d.idPS = a.idPS AND d.cnt = 1
APPEND SELECT 'PuntoDiLinea' nomeTipoEnte, b.nome nomeEnte, a.idPS idPS, 'SI' escludibile, e.cifra cifra
INTO tmpPunti
FROM punto_confine a, punto b, tmpPuntoConfineCount c, tmpPuntoConfineCountCifra d, tmpPuntoConfineCifra e
WHERE a.idPS = b.idPS AND a.idPS = c.idPS AND c.cnt = 2 AND d.idPS = a.idPS AND d.cnt != 1 AND e.idPS = d.idPS
APPEND SELECT 'Punto' nomeTipoEnte,b.nome nomeEnte,a.idPS idPS,'NO' escludibile, '-' cifra
INTO tmpPunti
FROM punto_tronco a,punto b
WHERE a.idPS=b.idPS
APPEND SELECT 'Punto' nomeTipoEnte,b.nome nomeEnte,a.idPS idPS,'NO' escludibile, a.cifre cifra
INTO tmpPunti
FROM punto_stazionamento a,punto b
WHERE a.idPS=b.idPS



SELECT DISTINCT a.nome nome, a.nome_alt nome_alt,  a.id_itine id_itine, count(a.nome) n
INTO tmpITALT_n
FROM itinerari a, itinerari b
WHERE a.po=b.po AND a.pf=b.pf
GROUP BY a.nome

SELECT DISTINCT a.nome nome, a.nome_alt nome_alt,  a.id_itine id_itine
INTO tmpITALT
FROM tmpITALT_n a
WHERE a.n!=1
        SELECT DISTINCT a.nome nome, a.nome_alt nome_alt,  a.id_itine id_itine
INTO tmpITNOALT
FROM tmpITALT_n a
WHERE a.n=1

APPEND SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoPlurimo' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivoPluECasc
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo!='LINEA'
AND i.stato='1' AND i.opzionale='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoPlurimo' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivoPluECasc
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo!='LINEA'
AND i.stato='1' AND i.opzionale!='-'


APPEND  SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoCascata' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivoPluECasc
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo!='LINEA'
AND cs.idSA=s1.idPS AND cs.testo='RIP'  AND i.stato='1' AND i.opzionale='-'


APPEND  SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoCascata' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivoPluECasc
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo!='LINEA'
AND cs.idSA=s1.idPS AND cs.testo='RIP'  AND i.stato='1' AND i.opzionale!='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoCascataCodificato' nomeTipoEnte, i.direzione direzione,'SI' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivoPluECasc
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa!='-'
 AND p.tipo!='LINEA' AND cs.idSA=s1.idPS AND cs.testo='RIP'  AND i.stato='1' AND i.opzionale='-'


APPEND SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoCascataCodificato' nomeTipoEnte, i.direzione direzione,'SI' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivoPluECasc
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa!='-'
AND p.tipo!='LINEA' AND cs.idSA=s1.idPS AND cs.testo='RIP'  AND i.stato='1' AND i.opzionale!='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoPlurimo' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivoPluECasc
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo!='LINEA'
AND cs.idSA=s1.idPS AND cs.testo!='RIP'  AND i.stato='1' AND i.opzionale='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoPlurimo' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivoPluECasc
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo!='LINEA'
AND cs.idSA=s1.idPS AND cs.testo!='RIP'  AND i.stato='1' AND i.opzionale!='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoPlurimoCodificato' nomeTipoEnte, i.direzione direzione,'SI' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivoPluECasc
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa!='-' AND p.tipo!='LINEA' AND cs.idSA=s1.idPS
AND cs.testo!='RIP'  AND i.stato='1' AND i.opzionale='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoPlurimoCodificato' nomeTipoEnte, i.direzione direzione,'SI' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivoPluECasc
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa!='-' AND p.tipo!='LINEA' AND cs.idSA=s1.idPS
AND cs.testo!='RIP'  AND i.stato='1' AND i.opzionale!='-'

SELECT DISTINCT a.nome nome,a.id_itine id_itine,a.PO PO, a.PF PF, a.saPO saPO,a.saPF saPF, a.cdb_occ cdb_occ, a.nomeTipoEnte nomeTipoEnte, a.direzione direzione,a.codificato codificato, a.ct ct,a..nome_ext nome_ext,a.schema schema, count(nome) index
INTO tmpItArrPlECs_Count
FROM  tmpITArrivoPluECasc a,tmpITArrivoPluECasc b WHERE a.id_itine=b.id_itine AND a.PO=b.PO AND a.PF=b.PF AND a.nome=b.nome
GROUP BY a.nome
      
SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoCodificato' nomeTipoEnte, i.direzione direzione,'SI' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa!='-' AND p.tipo='LINEA' AND i.stato='1' AND i.opzionale='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoCodificato' nomeTipoEnte, i.direzione direzione,'SI' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa!='-' AND p.tipo='LINEA' AND i.stato='1' AND i.opzionale!='-'


APPEND SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoPlurimoCodificato' nomeTipoEnte, i.direzione direzione,'SI' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa!='-' AND p.tipo!='LINEA' AND cs.idSA=s1.idPS
AND cs.testo!='RIP'  AND i.stato='1' AND i.opzionale='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoPlurimoCodificato' nomeTipoEnte, i.direzione direzione,'SI' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa!='-' AND p.tipo!='LINEA' AND cs.idSA=s1.idPS
AND cs.testo!='RIP'  AND i.stato='1' AND i.opzionale!='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivo' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod,tc_it_forblo_CtrSegnali cs,luce_segnale_alto ls
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente'
AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo='LINEA'  AND i.stato='1' AND cs.id_itine=i.id_itine AND ls.idPS=cs.id_segnale AND s2.idPS=ls.id_segnalealto  AND i.opzionale='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivo' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod,tc_it_forblo_CtrSegnali cs,luce_segnale_alto ls
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf
AND p.id_sa=s1.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente'
AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo='LINEA'  AND i.stato='1' AND cs.id_itine=i.id_itine AND ls.idPS=cs.id_segnale AND s2.idPS=ls.id_segnalealto  AND i.opzionale!='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoPlurimo' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo!='LINEA'
AND cs.idSA=s1.idPS AND cs.testo!='RIP'  AND i.stato='1' AND i.opzionale='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoPlurimo' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo!='LINEA'
AND cs.idSA=s1.idPS AND cs.testo!='RIP'  AND i.stato='1' AND i.opzionale!='-'

APPEND  SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoProsecuzione' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo!='LINEA'
AND cs.idSA=s1.idPS AND cs.testo='RIP'  AND i.stato='1' AND i.descrizione='PROSECUZIONE' AND i.opzionale='-'

APPEND  SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoProsecuzione' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo!='LINEA'
AND cs.idSA=s1.idPS AND cs.testo='RIP'  AND i.stato='1' AND i.descrizione='PROSECUZIONE' AND i.opzionale!='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoCascataCodificato' nomeTipoEnte, i.direzione direzione,'SI' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa!='-'
 AND p.tipo!='LINEA' AND cs.idSA=s1.idPS AND cs.testo='RIP'  AND i.stato='1' AND i.descrizione='CASCATA' AND i.opzionale='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoCascataCodificato' nomeTipoEnte, i.direzione direzione,'SI' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa!='-'
 AND p.tipo!='LINEA' AND cs.idSA=s1.idPS AND cs.testo='RIP'  AND i.stato='1' AND i.descrizione='CASCATA' AND i.opzionale!='-'

APPEND  SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoCascata' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo!='LINEA'
AND cs.idSA=s1.idPS AND cs.testo='RIP'  AND i.stato='1' AND i.descrizione='CASCATA' AND i.opzionale='-'

APPEND  SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, s1.nome saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoCascata' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,segnale_alto s1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod, tmpCartelloSA cs
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.id_sa=s1.idPS AND p2.id_sa=s2.idPS AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND cod.id_itine=i.id_itine AND cod.stringa='-' AND p.tipo!='LINEA'
AND cs.idSA=s1.idPS AND cs.testo='RIP'  AND i.stato='1' AND i.descrizione='CASCATA' AND i.opzionale!='-'

APPEND SELECT DISTINCT a.nome nome,a.id_itine id_itine,a.PO PO,a.PF PF,a.saPO saPO,a.saPF saPF,a.cdb_occ cdb_occ,a.nomeTipoEnte nomeTipoEnte,a.direzione direzione,a.codificato codificato,a.ct ct,a.nome_ext nome_ext,a.schema schema
INTO tmpITArrivo
FROM tmpItArrPlECs_Count a WHERE a.index=1

APPEND SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, b1.nome+'_VIRT' saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivo' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,blocco b1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.idPS=b1.id_scudetto AND p2.id_sa=s2.idPS AND b1.tipo='CONSENSO' AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND i.stato='1' AND i.opzionale='-' AND cod.id_itine=i.id_itine AND cod.stringa='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') + '_' + i.opzionale nome,i.id_itine,p.nome PO, p2.nome PF, b1.nome+'_VIRT' saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivo' nomeTipoEnte, i.direzione direzione,'NO' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,blocco b1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.idPS=b1.id_scudetto AND p2.id_sa=s2.idPS AND b1.tipo='CONSENSO' AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND i.stato='1' AND i.opzionale!='-' AND cod.id_itine=i.id_itine AND cod.stringa='-'

APPEND SELECT DISTINCT translate(i.nome,'-','_') nome,i.id_itine,p.nome PO, p2.nome PF, b1.nome+'_VIRT' saPO,s2.nome saPF, occ.nome cdb_occ, 'ItinerarioArrivoCodificato' nomeTipoEnte, i.direzione direzione,'SI' codificato, i.ct ct,i.nome+'('+i.nome_alt+')' nome_ext,'V401' schema
INTO tmpITArrivo
FROM itinerari i, punto p, punto p2,blocco b1,segnale_alto s2, tc_it_forblo_CdbPercorso occ, tc_it_manseg_coditine cod
WHERE i.tipo='ARRIVO' AND p.idPS=i.po AND p2.idPS=i.pf AND p.idPS=b1.id_scudetto AND p2.id_sa=s2.idPS AND b1.tipo='CONSENSO' AND occ.id_itine=i.id_itine AND occ.tipo='occupazione permanente' AND i.stato='1' AND i.opzionale='-' AND cod.id_itine=i.id_itine AND cod.stringa!='-'

In .NET 4.5, the execution takes ~0.650 seconds; in .NET 8.0, it takes ~4.5 seconds.

Can you help me to understand why this happens? Since it's legacy code I don't want to rewrite it in a new .NET 8.0 friendly way, I want to undestand if I can port this code to a new framework without modifying it too much.

Note: the code should not be culture sensitive.


Solution

  • Your performance difference seems to have arisen from a change made in .NET 5, specifically a switch to the International Components for Unicode (ICU) localization library. From .NET globalization and ICU:

    .NET globalization and ICU

    Before .NET 5, the .NET globalization APIs used different underlying libraries on different platforms. On Unix, the APIs used International Components for Unicode (ICU), and on Windows, they used National Language Support (NLS). This resulted in some behavioral differences in a handful of globalization APIs when running applications on different platforms...

    ICU on Windows

    Windows now incorporates a preinstalled icu.dll version as part of its features that's automatically employed for globalization tasks. This modification allows .NET to use this ICU library for its globalization support.

    If you switch back to NLS as shown in Use NLS instead of ICU,e.g. by editing your project file as follows, you should restore the previous performance.

    <ItemGroup>
      <RuntimeHostConfigurationOption Include="System.Globalization.UseNls" Value="true" />
    </ItemGroup>
    

    That being said, in comments you stated that GetTokens(string text) should not be localized. If that is the case, you need to modify your code as follows, as some of the string methods you are using are localized:

    1. Pass StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase to all calls to string.IndexOf(string, StringComparison).

    2. Pass StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase into string.StartsWith(string, StringComparison).

    3. Replace string.ToUpper() with string.ToUpperInvariant().

    4. Pass StringComparer.Ordinal or StringComparer.OrdinalIgnoreCase into all Enumerable.Contains<string>(, StringComparer) calls, e.g.:

      var comparer = _caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;
      
      if (_keyWords.Contains(tk, comparer))
          token.TokenType = ZQLTokenType.KeyWord;
      

    If you do this, switching back to NLS will not be required, and performance overall seems much faster. Demo fiddle here.

    Finally, there are many ways this code could be optimized, for instance:

    • Using a HashSet<string> for your keywords and operators.

    • For large strings, you may end up allocating huge amounts of temporary memory when you do:

      rText = rText.Substring(idx + ftk.Length);

      You could replace this with a Span<char> or Memory<char> and eliminate this substring entirely.

    However, this analysis is outside the scope of the question.

    Full code here:

    public class myclass
    {
        int _lineCounter = 0;
        int _indexCounter = 0;
        char[] _separators = new char[] { ' ', '.', ',', '\n', '\t', '\r', '(', ')' };
        static string[] _operators = new string[] { "!=", "=", "<>", "<", ">", "+" };
        static string[] _lOperators = new string[] { "!=", "=", "<>", "<", ">", "+" };
    
        bool _caseSensitive = false;
        List<string> _keyWords = new List<string>(new string[] { "NOT_EMPTY", "DISTINCT", "TABLE", "DROP", "APPEND", "SELECT", "WHERE", "FROM", "AND", "OR", "AS", "INTO", "XML_ATT", "XML_VAL", "GROUP", "BY", "ORDER", "NUMBER" });
    
        public List<ZQLToken> GetTokens(string text)
        {
            List<ZQLToken> ret = new List<ZQLToken>();
            string rText = text;
    
            _indexCounter = 0;
            _lineCounter = 1;
    
            string[] tks = text.Split(_separators, StringSplitOptions.RemoveEmptyEntries);
    
            foreach (string ftk in tks)
            {
                char pSep = '\0';
                char nSep = '\0';
    
                int idx = rText.IndexOf(ftk, StringComparison.Ordinal);
                if (idx > 0)
                    pSep = rText[idx - 1];
                if (idx + ftk.Length < rText.Length)
                    nSep = rText[idx + ftk.Length];
    
                rText = rText.Substring(idx + ftk.Length);
                _indexCounter = text.IndexOf(rText, StringComparison.Ordinal);
    
                string[] moreTk = ftk.Split(_operators, StringSplitOptions.RemoveEmptyEntries);
    
                string startOp = "";
                foreach (string oper in _operators)
                {
                    if (ftk.StartsWith(oper, StringComparison.Ordinal))
                        startOp = oper;
                }
    
                if (startOp != "")
                {
                    string tok = ftk.Substring(startOp.Length);
                    AddToken(ret, startOp, pSep, '\0');
                    AddToken(ret, tok, '\0', nSep);
                }
                else if (moreTk.Length >= 2)
                {
                    string rFtk = ftk;
                    string tk1 = moreTk[0];
                    AddToken(ret, tk1, pSep, '\0');
                    for (int i = 1; i < moreTk.Length; i++)
                    {
                        string tk2 = moreTk[i];
                        int idx2 = rFtk.IndexOf(tk2, tk1.Length, StringComparison.Ordinal);
    
                        string op = rFtk.Substring(tk1.Length, Math.Abs(idx2 - tk1.Length));
    
                        AddToken(ret, op, '\0', '\0');
    
                        if (i + 1 == moreTk.Length)
                            AddToken(ret, tk2, '\0', nSep);
                        else
                            AddToken(ret, tk2, '\0', '\0');
    
                        rFtk = rFtk.Substring(idx2);
                        tk1 = tk2;
                    }
                }
                else
                    AddToken(ret, moreTk[0], pSep, nSep);
            }
    
            return ret;
        }
    
        private void AddToken(List<ZQLToken> ret, string tk, char pSep, char nSep)
        {
            if (tk == "")
                return;
    
            ZQLToken token = new ZQLToken();
            token.Line = _lineCounter;
            token.Index = _indexCounter;
            if (ret.Count > 0)
            {
                ZQLToken ptok = ret[ret.Count - 1];
                ptok.NextToken = token;
                token.PrevToken = ptok;
            }
    
            token.PrevSeparator = pSep;
            token.NextSeparator = nSep;
    
            var comparer = _caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;
            
            if (_keyWords.Contains(tk, comparer))
                token.TokenType = ZQLTokenType.KeyWord;
            else if (_lOperators.Contains(tk, comparer))
                token.TokenType = ZQLTokenType.Operator;
            else
                token.TokenType = ZQLTokenType.Variable;
    
            token.Text = tk;
    
            if (token.NextSeparator == '\n')
                _lineCounter++;
    
            ret.Add(token);
        }
    }
    

    Update OK, I made some benchmarks using BenchmarkDotNet for the following test class:

    public class TestClass
    {
        [Benchmark]
        public void Test_myClass_GetTokens()
        {
            var text = GetInput(); // The input string from the question
            
            var myClass = new myclass();
            var list = myClass.GetTokens(text);
    
            Assert.That(list.Count == 4821);
        }
    

    Here are the results on .NET 8, x64, Release build:

    1. Original code, ICU:

      | Method                 | Mean    | Error    | StdDev   |
      |----------------------- |--------:|---------:|---------:|
      | Test_myClass_GetTokens | 3.778 s | 0.0420 s | 0.0372 s |
      
    2. Original code, NLS:

      | Method                 | Mean     | Error   | StdDev  |
      |----------------------- |---------:|--------:|--------:|
      | Test_myClass_GetTokens | 246.9 ms | 2.78 ms | 2.60 ms |
      
    3. Fixed Ordinal code from my answer, ICU:

      | Method                 | Mean     | Error    | StdDev   |
      |----------------------- |---------:|---------:|---------:|
      | Test_myClass_GetTokens | 17.62 ms | 0.218 ms | 0.182 ms |
      

    As you can see, ICU is roughly 15 times slower than NLS for the original code in the question -- but switching to ordinal (using ICU) is 14 times faster than the original, localized code using NLS. And since the method isn't supposed to be culture-sensitive anyway, using Ordinal or OrdinalIgnoreCase everywhere would seem to be the correct fix in both .NET Core and .NET Framework.